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

GridView - Issues with scrolling and internal Drag&Drop



  • With Qt 5.15.2 and QtQuick controls 2.0, I'm trying to create a photo view, which may support a vertical scrolling to navigate between many items, and allows also any item to be swapped with an internal drag&drop.

    I have a solution which roughly works. Below is the code.

    WQTMainForm.qml

    import QtQuick 2.15
    import QtQuick.Window 2.15
    import QtQuick.Controls 2.15
    
    /**
    * Application main window
    *@author JMR
    */
    Window
    {
        // advanced properties
        property var m_Model: ListModel {}
    
        // common properties
        visible: true
        width: 640
        height: 480
        title: qsTr("Photo view")
    
        /**
        * Background rect
        */
        Rectangle
        {
            // common properties
            anchors.fill: parent
            anchors.margins: 10
    
            /**
            * Background mouse area
            */
            MouseArea
            {
                // common properties
                anchors.fill: parent
                hoverEnabled: true
    
                /**
                * Photo grid view
                */
                GridView
                {
                    // common properties
                    id: gvPhotos
                    //interactive: false
                    anchors.fill: parent
                    cellWidth: 120
                    cellHeight: 120
                    flickableDirection: Flickable.VerticalFlick
                    keyNavigationEnabled: true
                    highlightFollowsCurrentItem: true
                    activeFocusOnTab: false
                    focus: true
                    clip: true
    
                    // model
                    model: m_Model
    
                    // item delegate
                    delegate: WQTPhotoItem
                    {}
    
                    /**
                    * Vertical scrollbar
                    */
                    ScrollBar.vertical: ScrollBar
                    {
                        // common properties
                        id: sbFileListView
                        parent: gvPhotos
                        visible: true
                        minimumSize: 0.1
                    }
    
                    /**
                    * Grid mouse area (for drag&drop)
                    */
                    MouseArea
                    {
                        // advanced properties
                        property int rowCount: parseInt(gvPhotos.width / gvPhotos.cellWidth)
                        property int mX: (mouseX < 0.0) ? 0 : (mouseX > gvPhotos.width)  ? gvPhotos.width : mouseX
                        property int mY: (mouseY < 0.0) ? 0 : (mouseY > gvPhotos.height) ? gvPhotos.width : mouseY
                        property int index: parseInt((gvPhotos.contentY + mY) / gvPhotos.cellHeight) * rowCount + parseInt(mX / gvPhotos.cellWidth)
                        property int activeIndex: -1
    
                        // common properties
                        id: maGridView
                        anchors.fill: parent
                        hoverEnabled: true
                        drag.axis: Drag.XandYAxis
    
                        // called when the mouse button is pressed and maintained for a small time
                        onPressAndHold:
                        {
                            gvPhotos.currentIndex  = index;
                            gvPhotos.currentItem.z = 10;
                            drag.target            = gvPhotos.currentItem;
                            activeIndex            = index;
                        }
    
                        // called when the mouse button is released after a press (and hold)
                        onReleased:
                        {
                            var targetIndex = index;
    
                            if (targetIndex < 0)
                                targetIndex = 0;
                            else
                            if (targetIndex >= gvPhotos.count)
                                targetIndex = gvPhotos.count - 1;
    
                            gvPhotos.currentItem.x =  gvPhotos.cellWidth  *         (targetIndex % rowCount);
                            gvPhotos.currentItem.y =  gvPhotos.cellHeight * parseInt(targetIndex / rowCount);
                            gvPhotos.currentItem.z =  1;
                            //REM console.log(targetIndex + " - " + gvPhotos.currentItem.z);//REM
                            gvPhotos.currentIndex  = -1;
                            activeIndex            = -1;
                            drag.target            =  null;
                        }
    
                        // called when mouse cursor position changed in the mouse area
                        onPositionChanged:
                        {
                            if (drag.active && index >= 0 && index < gvPhotos.count && index !== activeIndex)
                                gvPhotos.model.move(activeIndex, activeIndex = index, 1);
                        }
                    }
                }
    
                // called when the mouse enters in the grid
                onEntered: function()
                {
                    console.log("Mouse entered the grid");
                }
    
                // called when the mouse leaves the grid
                onExited: function()
                {
                    console.log("Mouse exited the grid");
                }
            }
        }
    
        /**
        * Called after the main window object was created
        */
        Component.onCompleted:
        {
            // fill the grid view
            for (var i = 0; i < 1000; ++i)
                gvPhotos.model.append({"icon": "Images/widget" + ((i % 9) + 1) + ".png", "gridId": i});
        }
    }
    

    WQTPhotoItem:

    import QtQuick 2.15
    
    /**
    * Photo item
    *@author JMR
    */
    Component
    {
        /**
        * Item
        */
        Item
        {
            // advanced properties
            property bool m_Active: false
    
            // common properties
            id: itItem
            width: gvPhotos.cellWidth
            height: gvPhotos.cellHeight
    
            /**
            * Item background
            */
            Rectangle
            {
                // common properties
                /*
                x: itItem.x;
                y: itItem.y
                width: itItem.width;
                height: itItem.height;
                parent: gvPhotos
                */
                anchors.fill: parent
                color: "darkgray"
    
                /**
                * Item content background
                */
                Rectangle
                {
                    // common properties
                    anchors.fill: parent
                    anchors.margins: 5
                    color: "black"
    
                    /**
                    * Photo
                    */
                    Image
                    {
                        // common properties
                        id: imImage
                        anchors.fill: parent
                        fillMode: Image.PreserveAspectFit
                        smooth: true
                        source: icon
    
                        /**
                        * Photo outline
                        */
                        Rectangle
                        {
                            anchors.fill: parent
                            border.color: "#326487"
                            border.width: 6
                            color: "transparent"
                            radius: 5
                        }
    
                        /**
                        * Photo wobbling animation
                        */
                        SequentialAnimation on rotation
                        {
                            NumberAnimation {to:  2; duration: 60}
                            NumberAnimation {to: -2; duration: 120}
                            NumberAnimation {to:  0; duration: 60}
    
                            running: maGridView.activeIndex !== -1 && !m_Active
                            loops: Animation.Infinite
                            alwaysRunToEnd: true
                        }
    
                        /**
                        * Item state
                        */
                        states: State
                        {
                            name: "active"
                            when: maGridView.activeIndex === index
    
                            PropertyChanges
                            {
                                target: imImage
                                x: maGridView.mouseX - width  / 2
                                y: maGridView.mouseY - height / 2
                                scale: 0.5
                                z: 10
                            }
                        }
    
                        /**
                        * Animation transitions
                        */
                        transitions: Transition
                        {
                            NumberAnimation
                            {
                                property: "scale"
                                duration: 200
                            }
                        }
                    }
                }
    
                /**
                * Photo x animation
                */
                /*
                Behavior on x
                {
                    enabled: !m_Active
    
                    NumberAnimation
                    {
                        duration: 400
                        easing.type: Easing.OutBack
                    }
                }
                */
    
                /**
                * Photo y animation
                */
                /*
                Behavior on y
                {
                    enabled: !m_Active
    
                    NumberAnimation
                    {
                        duration: 400
                        easing.type: Easing.OutBack
                    }
                }
                */
            }
        }
    }
    

    The above view works as long as no special case happens. However there are several remaining issues I cannot solve, among which:

    1. When I try to drag an image before the first item or after the last item, the scroll continues infinitely, and the drop behavior becomes inconsistent and random.
    2. I cannot animate the x and y positions. I found several examples about how to do that, which I tried to implement (it's the commented code). However this solution works as long as the view isn't scrolled. But it breaks the scrolling completely.

    Can someone explain me what is wrong in my code, and how I should correct it to fix the still existing issues described above?

    Also I need to add a multi-selection in the above view, how can I achieve that?

    And finally, is there a better/more simpler way to reach the above described view? Or an existing example about a similar view?



  • I could find a solution for the issue #2 mentioned above, by using the moveDisplaced property of the GridView component, instead of trying to animate the items directly, like that:

    // animate the item move, when its x and/or y position changes
    moveDisplaced: Transition
    {
        NumberAnimation
        {
            properties: "x,y"
            duration: 200
        }
    }
    

    However the other questions remain opened.


Log in to reply