Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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 ?

    0_1560825097496_D_D.gif

    All the best



  • @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.


Log in to reply