Drag&Drop in QML/C++ mixed model/view
-
I have got working Qt/C++ model with QML implemented view using ListView. It works fine but now I want make available D&D operating over list items. It will not be just a visual reordering - list model will change after view item moved to another list position. Therefore I must implement some communication with C++ model. I read QML Dynamic View Ordering Tutorial chapter and now I see how change the QML code to allow D&D. But this tutorial uses QML implemented model therefore I do not see clearly how I should inform my model about D&D view changes. I must send to model the signal with information about two indexes - the index of item when drag was started and the index of item when it was dropped.
As I can imagine first I must declare signal in root (following uses code from tutorial):
Rectangle { id: root property int startIndex signal sendIndexes( int old, int new ) width: 300; height: 400 .....
In C++ code I attach slot to the signal. I already have got similar code working in other case.
Then in MouseArea I can store starting index and send signal when needed:MouseArea { id: dragArea ... onPressAndHold: { held = true startIndex = index } onReleased: { held = false root.sendIndexes( startIndex, index ) }
In receiving slot I can define if indexes are equal then no moving happen.
Is this enough or I must make something else? I am not sure if the index changes when onReleased handler is called. May be signal better emit in onDrop handler inside DropArea. Sorry, I am not a keen in QML, just studying it.
-
Hi @Gourmet
- Create a C++ class inheriting from
QAbstractListModel
Example :class MyModel : public QAbstractListModel
- Write a
Q_INVOKABLE void swapData(int _from, int _to = 0);
- Use beginMoveRows() & endMoveRows() ( you can even use beginMoveColumns() & endMoveColumns() )
void MyModel::swapData(int _from, int _to) { qDebug() << _from << _to << endl; if(_from != _to){ beginMoveRows(QModelIndex(), _from, _from, QModelIndex(), _to); m_List.move(_from, _to); endMoveRows(); } }
- Expose it to QML
MyModel *_model = new MyModel;
context->setContextProperty("myModel", _model);
- Use DropArea inside MouseArea {}
DropArea { anchors { fill: parent; margins: 10 } onEntered: { dragArea.fromVal = dragArea.DelegateModel.itemsIndex dragArea.toVal = drag.source.DelegateModel.itemsIndex visualModel.items.move(dragArea.fromVal, dragArea.toVal, 1) }
- MouseArea{}
onRelease:
truckModel.swapData(index, dragArea.toVal)
Is this what you expect the Drag & Drop ?
All the best
- Create a C++ class inheriting from
-
@Pradeep-P-N thanx but this is not fully applicable... I have already working model/view based on QList<myclass*> and QVariant. I just have not time to redesign it for QAbstractListModel, implement roles and so on. I can send signal from inside view to model with indexes of elements to be swapped. After swap I will just reset setContextProperty(). The only thing what I need is - proper place for this signal. Your example gives me idea for this.
-
I am weird... What is DelegateModel in case when model is written on C++? Here:
onEntered: { dragArea.fromVal = dragArea.DelegateModel.itemsIndex dragArea.toVal = drag.source.DelegateModel.itemsIndex visualModel.items.move(dragArea.fromVal, dragArea.toVal, 1) }
I know only model name in ListView but I do not have DelegateModel defined in QML.
-
First I want add scrolling while drag. This is not shown in any example. I have following code.
Item { id: root visible: true ListView { id: lView orientation: ListView.Vertical model: visualModel anchors.fill: parent DelegateModel { id: visualModel model: itemsList delegate: Rectangle { id: listItem color: tapArea.held ? "lightsteelblue" : modelData.Bought ? "darkgrey" : "yellow" Behavior on color { ColorAnimation { duration: 100 } } width: lView.width height: 200 border.width: 1 border.color: "blue" states: State { when: tapArea.held ParentChange { target: listItem; parent: root } AnchorChanges { target: listItem anchors { horizontalCenter: undefined; verticalCenter: undefined } } } Drag.active: tapArea.held Drag.source: tapArea Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: tapArea z: -1 anchors.fill: parent onDoubleClicked: { modelData.EditedIndex = index } property bool held: false property int lasty: 0 property bool moveUp: false onYChanged: { moveUp = lasty > y; lasty = y } drag.target: held ? listItem : undefined drag.axis: Drag.YAxis onPressAndHold: held = true onReleased: held = false DropArea { anchors { fill: parent; margins: 10 } onEntered: { console.log("entered drop area") visualModel.items.move( drag.source.DelegateModel.itemsIndex, tapArea.DelegateModel.itemsIndex ) lView.currentIndex = tapArea.DelegateModel.itemsIndex + (tapArea.moveUp ? -1 : 1) } } } } } ScrollBar.vertical: ScrollBar{ active: true width: 20 } } }
It almost woks - item is long selected by long tap and can be dragged but only inside window. Drag does not scroll when list is long. But I need it scrolling therefore tried implement this. I get console error
:-1 ((null)): <Unknown File>: QML DelegateModelGroup: move: invalid from index
each time when select item by long tap. Message "entered drop area" does not ever appear.
-
Things become more strange... I have added
DropArea { id: dropper
and made this :
delegate: Rectangle { border.color: dropper.containsDrag ? "red" : "blue"
As it's written in docs: "A DropArea is an invisible item which receives events when other items are dragged over it.". Over - means the containsDrag must become true when there is drug over this item. That means border of item under drugged must become red. But... border color becomes red around the dragged item - not under drugged. What the heck is going on? :-\
The same happiness when I change bool toggle inside onEntered handler and change border color depending from this toggle. It changes color of dragged item but not under drugged.
DropArea { console.warn(itemName.text)
Shows text of the dragged item - instead of item text under dragged.
I am finally weird. HELP!!!...
No anything method works in Android.