[Solved] QML and Drag and Drop
-
So i am trying to find a way to rearrange dashboard widgets for my application. I would to give the user the ability to do this by just pressing and holding on a widget and then dragging it to a new location. Here is an implementation that allows the user to tap the widget he or she wants to change and then move it by tapping a new location. It works but I would love to add drag and drop to this. Please let me know if you have any ideas:
@import Qt 4.7
Rectangle {
width: 640
height: 480
color: "#111111"Component { id: widgetdelegate Item { width: grid.cellWidth; height: grid.cellHeight Image { source: portrait; anchors.horizontalCenter: parent.horizontalCenter width: grid.cellWidth; height: grid.cellHeight fillMode: Image.PreserveAspectFit } MouseArea { id: mouse_area1 anchors.fill: parent onClicked: { if (grid.firstIndexDrag== 0) { grid.firstIndexDrag=index }else { widgetmodel.move(grid.firstIndexDrag,index,1) grid.firstIndexDrag= 0 } } } } } ListModel { id: widgetmodel ListElement { portrait: "Images/widget1.png" } ListElement { portrait: "Images/widget2.png" } ListElement { portrait: "Images/widget3.png" } ListElement { portrait: "Images/widget4.png" } ListElement { portrait: "Images/widget5.png" } ListElement { portrait: "Images/widget6.png" } ListElement { portrait: "Images/widget7.png" } ListElement { portrait: "Images/widget8.png" } ListElement { portrait: "Images/widget9.png" } } GridView { property int firstIndexDrag: 0 id: grid x: 0 y: 0 anchors.rightMargin: 200 anchors.bottomMargin: 100 anchors.leftMargin: 200 anchors.topMargin: 100 width: 640 height: 480 anchors.fill: parent cellWidth: 80; cellHeight: 80 flow: GridView.LeftToRight model: widgetmodel delegate: widgetdelegate //highlight: Rectangle { color: "white"; radius: 5 ; z: 1 } focus: true }
}
@ -
kyleplattner, is it related with "this other thread":http://developer.qt.nokia.com/forums/viewthread/2388?
-
Same in concept, different in approach to implementation.
-
Hey Kyle. Here is drag and drop for you:
For the sake of brevity, I have separated your widgetmodel in to another QML file which I won't repeat in future.
WidgetModel.qml
@
import QtQuick 1.0ListModel {
ListElement {
portrait: "Images/widget1.png"
}
ListElement {
portrait: "Images/widget2.png"
}
ListElement {
portrait: "Images/widget3.png"
}
ListElement {
portrait: "Images/widget4.png"
}
ListElement {
portrait: "Images/widget5.png"
}
ListElement {
portrait: "Images/widget6.png"
}
ListElement {
portrait: "Images/widget7.png"
}
ListElement {
portrait: "Images/widget8.png"
}
ListElement {
portrait: "Images/widget9.png"
}
}@Main.qml
@
import QtQuick 1.0Rectangle {
width: 640
height: 480
color: "#111111"
Component {
id: widgetdelegate
Item {
width: grid.cellWidth; height: grid.cellHeight
Image {
source: portrait;
anchors.horizontalCenter: parent.horizontalCenter
width: grid.cellWidth; height: grid.cellHeight
fillMode: Image.PreserveAspectFit
}
Rectangle {
width: parent.width; height: parent.height; radius: 5
border.color: "white"; color: "transparent"; border.width: 6;
visible: index == grid.firstIndexDrag
}
}
}GridView { property int firstIndexDrag: -1 id: grid x: 0; y: 0 interactive: false anchors.rightMargin: 200 anchors.bottomMargin: 100 anchors.leftMargin: 200 anchors.topMargin: 100 width: 640 height: 480 anchors.fill: parent cellWidth: 80; cellHeight: 80 model: WidgetModel { id: widgetmodel } delegate: widgetdelegate MouseArea { anchors.fill: parent onReleased: { if (grid.firstIndexDrag != -1) widgetmodel.move(grid.firstIndexDrag,grid.indexAt(mouseX, mouseY),1) grid.firstIndexDrag = -1 } onPressed: grid.firstIndexDrag=grid.indexAt(mouseX, mouseY) } }
}@
You probably want to animate the drag and drop too. But didn't we cover this in the other topic? Please tell me if you desire code for this too.
-
I am interested in the code for animating it, thanks. I will try this tonight.
I really appreciate your help.
Kyle
-
Ok, I used re-parenting to animate the drag:
@
import QtQuick 1.0Rectangle {
width: 640
height: 480
color: "#222222"
Component {
id: widgetdelegate
Item {
width: grid.cellWidth; height: grid.cellHeight
Image {
id: im
source: portrait;
anchors.centerIn: parent
width: grid.cellWidth - 10; height: grid.cellHeight - 10
smooth: true
fillMode: Image.PreserveAspectFit
Rectangle {
id: imRect
anchors.fill: parent; radius: 5
anchors.centerIn: parent
border.color: "#326487"; color: "transparent"; border.width: 6;
opacity: 0
}
}
Rectangle {
id: iWasHere
width: 20; height: 20; radius: 20
smooth: true
anchors.centerIn: parent
color: "white";
opacity: 0
}
states: [
State {
name: "inDrag"
when: index == grid.firstIndexDrag
PropertyChanges { target: iWasHere; opacity: 1 }
PropertyChanges { target: imRect; opacity: 1 }
PropertyChanges { target: im; parent: container }
PropertyChanges { target: im; width: (grid.cellWidth - 10) / 2 }
PropertyChanges { target: im; height: (grid.cellHeight - 10) / 2 }
PropertyChanges { target: im; anchors.centerIn: undefined }
PropertyChanges { target: im; x: coords.mouseX - im.width/2 }
PropertyChanges { target: im; y: coords.mouseY - im.height/2 }
}
]
transitions: [
Transition { NumberAnimation { properties: "width, height, opacity"; duration: 300; easing.type: Easing.InOutQuad } }
]
}
}GridView { property int firstIndexDrag: -1 id: grid x: 0; y: 0 interactive: false anchors.rightMargin: 200 anchors.bottomMargin: 100 anchors.leftMargin: 200 anchors.topMargin: 100 anchors.fill: parent cellWidth: 80; cellHeight: 80; model: WidgetModel { id: widgetmodel } delegate: widgetdelegate Item { id: container anchors.fill: parent } MouseArea { id: coords anchors.fill: parent onReleased: { if (grid.firstIndexDrag != -1) widgetmodel.move(grid.firstIndexDrag,grid.indexAt(mouseX, mouseY),1) grid.firstIndexDrag = -1 } onPressed: { grid.firstIndexDrag=grid.indexAt(mouseX, mouseY) } } }
}@
Enjoy!
-
Amazing, very well done. Thanks so much! I owe you a lot.
-
Oh by the way, I read your previous thread and remembered you wanted to have that 'squiggle' thing when the user does an 'onPressAndHold' -- like iOS does. So, I did this too. I hope you learn from the code:
Main.qml
@
import QtQuick 1.0Rectangle {
width: 640
height: 480
color: "#222222"
Component {
id: widgetdelegate
Item {
width: grid.cellWidth; height: grid.cellHeight
Image {
id: im
state: "inactive"
source: portrait;
anchors.centerIn: parent
width: grid.cellWidth - 10; height: grid.cellHeight - 10
smooth: true
fillMode: Image.PreserveAspectFit
SequentialAnimation on rotation {
NumberAnimation { to: 20; duration: 200 }
NumberAnimation { to: -20; duration: 400 }
NumberAnimation { to: 0; duration: 200 }
running: im.state == "squiggle"
loops: Animation.Infinite
}
Rectangle {
id: imRect
anchors.fill: parent; radius: 5
anchors.centerIn: parent
border.color: "#326487"; color: "transparent"; border.width: 6;
opacity: 0
}
states: [
State {
name: "squiggle";
when: (grid.firstIndexDrag != -1) && (grid.firstIndexDrag != index)
},
State {
name: "inactive";
when: (grid.firstIndexDrag == -1) || (grid.firstIndexDrag == index)
PropertyChanges { target: im; rotation: 0}
}
]
}
Rectangle {
id: iWasHere
width: 20; height: 20; radius: 20
smooth: true
anchors.centerIn: parent
color: "white";
opacity: 0
}
states: [
State {
name: "inDrag"
when: index == grid.firstIndexDrag
PropertyChanges { target: iWasHere; opacity: 1 }
PropertyChanges { target: imRect; opacity: 1 }
PropertyChanges { target: im; parent: container }
PropertyChanges { target: im; width: (grid.cellWidth - 10) / 2 }
PropertyChanges { target: im; height: (grid.cellHeight - 10) / 2 }
PropertyChanges { target: im; anchors.centerIn: undefined }
PropertyChanges { target: im; x: coords.mouseX - im.width/2 }
PropertyChanges { target: im; y: coords.mouseY - im.height/2 }
}
]
transitions: [
Transition { NumberAnimation { properties: "width, height, opacity"; duration: 300; easing.type: Easing.InOutQuad } }
]
}
}GridView { property int firstIndexDrag: -1 id: grid x: 0; y: 0 interactive: false anchors.rightMargin: 200 anchors.bottomMargin: 100 anchors.leftMargin: 200 anchors.topMargin: 100 anchors.fill: parent cellWidth: 80; cellHeight: 80; model: WidgetModel { id: widgetmodel } delegate: widgetdelegate Item { id: container anchors.fill: parent } MouseArea { id: coords anchors.fill: parent onReleased: { if (grid.firstIndexDrag != -1) widgetmodel.move(grid.firstIndexDrag,grid.indexAt(mouseX, mouseY),1) grid.firstIndexDrag = -1 } onPressAndHold: { grid.firstIndexDrag=grid.indexAt(mouseX, mouseY) } } }
}@
This has been made in to a Wiki Entry:
http://developer.qt.nokia.com/wiki/Drag_and_Drop_within_a_GridView -
-
I'm responsible for the second version. I tried to update this post with a "I’ve updated the above wiki entry with an alternate implementation." like I did on the "other thread":http://developer.qt.nokia.com/forums/viewreply/14354/, but it failed for some reason.
As it turns out, the second version requires Qt 4.7.1. It gives that error with Qt 4.7.0. Thanks for pointing it out. I wasn't aware of this difference in the versions. I've updated the wiki page with a notation about this.
-
By the way, I just fixed in a critical issue with the first version in the wiki. It was to do with the moving code.
Problem was discovered and fixed in this thread: http://developer.qt.nokia.com/forums/viewthread/2460/P15/The issue was that if you dragged items forward, the call to 'move' would push the indices backwards (to -1, -2 and so on) and you would no longer be able to drag the '-1' index and other items would become out of whack.
It is most noticeable on a 3-item grid.
I think the issue is only if you don't have animation onMove. The second example uses such an animation so it should be fine. Just be careful if you disable the animation.
-
Thank you guys. I finally get the second version working.
It turns out that "gridId" should be defined in WidgetModel.qml, and it must starts with 0. For example:
@
ListElement { icon: "images/widget1.png"; gridId: 0 }
ListElement { icon: "images/widget2.png"; gridId: 1 }
ListElement { icon: "images/widget3.png"; gridId: 2 }
@ -
Still returning:
@file:///Users/kp/Desktop/Precision Work/Screen Design/FinalScreens/QMLFinal/Rearrange4.qml:22: TypeError: Result of expression 'grid.items' [undefined] is not an object.@
-
I have made a cleaner version of the 'second version'
Halved the lines of code, removed quite a lot of redundancy and doesn't require Qt4.7.1.
Personally, I think the grids list is a bit silly when it can be done with the model/delegate. So I have completely removed the IconItem and grids.
No loss of functionality.
I replaced the first version with it. You can check it out in the wiki. -
Yeah, I know, you can't animate a GridList.
I discussed this "in another thread.":http://developer.qt.nokia.com/forums/viewthread/2327/#13163Basically, you need to reparent the items within the delegate. Then you have complete freedom of movement and animation. I reparented the images to the MouseArea.