[Solved] QML and Drag and Drop
-
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.
-
Yeah it already does that.
Anchor is fill to parent and parent width is whatever the host operating system gives it. 640x480 is just for reference.
Try it out now?
I think the qmlviewer on Windows won't allow you to resize the window smaller than 640x480 by the way. Might need a custom qmlviewer for that. -
As I can see, the size of qmlViewer will be the same as the top item in the qml file when it starts.
Now I'm thinking of storing position of these icons.
I guess the position should be stored in the model(or somewhere else?). How to show icons in GridView according to the values stored in the model?
-
Well you actually cannot change the positions of items inside a GridView.
What I have done is created items within the GridView and then reparented them to a MouseArea (outside of GridView) so that I can move them freely.
Then I assign the x,y values of the GridView locations.If you were to create entirely new locations and not use a GridView at all, you may as well use a Flow element.
If you're just using GridView/ListView for the model/delegate, then I will answer your question:
In your model, create an 'x' and 'y' property. For example 'myx', 'myy'. Then in the image, just set:
@x=myx; y=my@