[Solved] QML and Drag and Drop
-
wrote on 14 Dec 2010, 22:23 last edited by
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 }
}
@ -
wrote on 14 Dec 2010, 22:30 last edited by
kyleplattner, is it related with "this other thread":http://developer.qt.nokia.com/forums/viewthread/2388?
-
wrote on 14 Dec 2010, 22:41 last edited by
Same in concept, different in approach to implementation.
-
wrote on 14 Dec 2010, 23:40 last edited by
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.
-
wrote on 14 Dec 2010, 23:45 last edited by
I am interested in the code for animating it, thanks. I will try this tonight.
I really appreciate your help.
Kyle
-
wrote on 15 Dec 2010, 00:34 last edited by
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!
-
wrote on 15 Dec 2010, 00:49 last edited by
Amazing, very well done. Thanks so much! I owe you a lot.
-
wrote on 15 Dec 2010, 01:07 last edited by
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 -
wrote on 16 Dec 2010, 08:52 last edited by
Hi Sacha,
The demo of drag and drop code looks cool!
I tried the code(Main2.qml) of second version in the wiki but failed with following error: "ReferenceError: Can't find variable: gridId"
Is there anything I can do to avoid this?
THanks
-
wrote on 16 Dec 2010, 13:39 last edited by
Well, I didn't write the second version.
I tested my (first) version and provided a video of that exact code running.I can review the second one for you to discover the issue. It seems a lot better than my version so I hope you can get it working.
-
wrote on 16 Dec 2010, 16:58 last edited by
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.
-
wrote on 16 Dec 2010, 17:34 last edited by
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.
-
wrote on 17 Dec 2010, 00:54 last edited by
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 }
@ -
wrote on 17 Dec 2010, 01:07 last edited by
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.@
-
wrote on 17 Dec 2010, 03:32 last edited by
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. -
wrote on 17 Dec 2010, 18:29 last edited by
The grids list was made because when I added behaviors in the delegate, it wasn't working. I don't know why it wasn't working for me, but what you've got is working. I'll remove the second version now that it is obsolete.
-
wrote on 17 Dec 2010, 23:05 last edited by
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.
-
wrote on 18 Dec 2010, 08:16 last edited by
Looks like in the new code the grid can't be resized, it always be 3*3.
For example if I drag the size of its parent to a long list, it should be automatically rearranged to a 1*9 grid, as the GridView will do.
-
wrote on 18 Dec 2010, 08:59 last edited by
When you drag the window?
Oh I had removed that as I was running it on a mobile device. I'll add that back in, thanks. -
wrote on 18 Dec 2010, 09:09 last edited by
Just get it works by simply set width and height of GridView equal to its parent. :)
4/42