qtdrag-and-dropqmlqtquick2listmodel

Drag and drop multiple items in Qml


I need to draw bunch of Rectangles in my application. The user should be able to select each of them individually and move them freely and change their position. The user also should be able to select multiple Rectangles and move all the selected one simultaneously and release them somewhere else. I could already implement something based on Gridview that can handle selection and movement of one Rectangle, but I cannot make it work for multiple selection/movement. Here is a snippet of code I have currently:

GridView { 
        id: mainGrid
        cellWidth: 7;
        cellHeight: 7;

        ListModel {
            id: myModel
            function createModel() {
                for(var i = 0; i < totalZoneList.length; i++)
                {
                    for (var j = 0; j < moduleZoneList.length; j++)
                    {
                         myModel.append({"item1": ITEM1, "item2": ITEM2})
                    }
                }
            }
            Component.onCompleted: {createModel()}
        }

        Component { 
            id: myblocks
            Item {
                id: item
                width: mainGrid.cellWidth; 
                height: mainGrid.cellHeight;
                Rectangle {
                    id: box
                    parent: mainGrid
                    x: //CALCULATED BASED ON MODEL
                    y: //CALCULATED BASED ON MODEL
                    width: //CALCULATED BASED ON MODEL
                    height: //CALCULATED BASED ON MODEL


                    MouseArea {
                        id: gridArea
                        anchors.fill: parent
                        hoverEnabled: true
                        drag.axis: Drag.XandYAxis
                        drag.minimumX: 0
                        drag.minimumY: 0


                        property int mX: (mouseX < 0) ? 0 : ((mouseX < mainGrid.width - mainGrid.cellWidth) ? mouseX : mainGrid.width - mainGrid.cellWidth)
                        property int mY: (mouseY < 0) ? 0 : ((mouseY < mainGrid.height - mainGrid.cellHeight) ? mouseY : mainGrid.height - mainGrid.cellHeight)
                        property int index: parseInt(mX/mainGrid.cellWidth) + 5*parseInt(mY/mainGrid.cellHeight)  //item underneath cursor
                        property int activeIndex
                        property var xWhenPressed
                        property var yWhenPressed
                        propagateComposedEvents: true

                        onPressed: {
                            activeIndex = index
                            drag.target = box
                            xWhenPressed = box.x
                            yWhenPressed = box.y

                            gridArea.drag.maximumX = mainGrid.width - box.width
                            gridArea.drag.maximumY = mainGrid.height - box.height
                        }
                        onReleased: {
                           if(xWhenPressed !== box.x || yWhenPressed !== box.y)
                            {
                              //RECALCULATE THE POSITION
                            }
                        }
                        onPositionChanged: {
                            if (drag.active && index !== -1 && index !== activeIndex) {
                                                            mainGrid.model.move(activeIndex, activeIndex = index, 1)
                            }
                        }
                    } // Mousearea
                } // Rectangle
            } // Item
        } // Component

    } //mainGrid

Solution

  • I didn't manage to have your code working. First, I see mistakes on it:

    Rectangle {
        id: box
        parent: mainGrid
    ...
    }
    

    you just need to remove the parent Item which is of no use, and set the Rectangle as root of the delegate.

    Then, you forgot to mention that the target of drag is the Rectangle

    drag.target: parent
    

    Here is the code after correcting:

    Component {
        id: myblocks
    
        Rectangle {
            id: box
            color: "red"
            width: 20
            height: 20
    
            MouseArea {
                id: gridArea
                anchors.fill: parent
                drag.target: parent
                hoverEnabled: true
                drag.axis: Drag.XandYAxis
            } // Mousearea
        } // Rectangle
    } // Component
    

    Then, you shouldn't use a GridView because you want elements to be moved. If you use a Repeater it works, and you just have to set x and y in the Rectangle to place the elements at the beginning.

    Now this is a solution for your problem: you click on an element to select it, and you can move all selected items at once.

    Window {
        visible: true
        width: 640
        height: 480
    
        property var totalZoneList: ["total1", "total2"]
        property var moduleZoneList: ["module1", "module2"]
    
        Repeater{
            id: iRepeater
            model: ListModel {
                        id: myModel
                        function createModel() {
                            for(var i = 0; i < totalZoneList.length; i++)
                            {
                                for (var j = 0; j < moduleZoneList.length; j++)
                                {
                                    myModel.append({"item1": totalZoneList[i], "item2": moduleZoneList[j]})
                                }
                            }
                        }
                        Component.onCompleted: {createModel()}
                    }
            delegate: myblocks
    
        }
    
        Component {
            id: myblocks
    
            Rectangle {
                id: box
                color: {
                    switch(index){
                    case 0: selected ? "red" : "#FF9999";break;
                    case 1: selected ? "blue" : "lightblue";break;
                    case 2: selected ? "green" : "lightgreen";break;
                    case 3: selected ? "grey" : "lightgrey";break;
                    }
                }
                x: (width + 5)*index
    
                width: 20
                height: 20
                property int offsetX:0
                property int offsetY:0
                property bool selected: false
                function setRelative(pressedRect){
                    disableDrag();
                    x = Qt.binding(function (){ return pressedRect.x + offsetX; })
                    y = Qt.binding(function (){ return pressedRect.y + offsetY; })
                }
                function enableDrag(){
                    gridArea.drag.target = box
                }
                function disableDrag(){
                    gridArea.drag.target = null
                }
    
                MouseArea {
                    id: gridArea
                    anchors.fill: parent
                    hoverEnabled: true
                    drag.axis: Drag.XandYAxis
                    onClicked: parent.selected=!parent.selected
    
                    onPressed: {
    
                        var pressedRect = iRepeater.itemAt(index);
                        if (pressedRect.selected == true){
                            for (var i=0; i<iRepeater.count; i++ ){
                                var rect = iRepeater.itemAt(i);
                                if (i != index){
                                    //init for breaking existing binding
                                    rect.x = rect.x
                                    rect.y = rect.y
                                    rect.disableDrag()
                                    if (rect.selected == true){
                                        rect.offsetX = rect.x - pressedRect.x
                                        rect.offsetY = rect.y - pressedRect.y
                                        rect.setRelative(pressedRect)
                                    }
                                }
                            }
                            pressedRect.enableDrag()
                        }
                    }
                } // Mousearea
            } // Rectangle
        } // Component
    }