user2366975
user2366975

Reputation: 4700

Update c++ model on qml delegate visual model change

There's a simple QStandardItemModel defined in c++ which I am displaying in a QML ListView via custom Delegates and a DelegateModel. The ListView can be reordered via Drag'n Drop:

// The DropArea is part of the delegate `comp_container`
DropArea{
    anchors{fill: parent}
    keys: ["pageitem"]
    onEntered: {
        let from = drag.source.DelegateModel.itemsIndex
        let to = dragAreaPage.DelegateModel.itemsIndex
        if ( pageItemDragOperationStartIndex === -1 ){
            pageItemDragOperationStartIndex = from
        }
        pageItemDragOperationFinalIndex = to
        console.log(from + "->" + to)
        visualModel.items.move(from,to)
    }
}

Here is the delegate model and pageproxymodel is the c++ model.

DelegateModel {
    id: visualModel
    model: pageproxymodel
    delegate: comp_container
}

How do I want to update the c++ model? The delegate's top level item is a MouseArea and I handle the reordering in the release handler:

onReleased: {
    if ( pageItemDragOperationStartIndex !== -1 && pageItemDragOperationFinalIndex !== -1 ){
        console.log("Page item final drag operation: " + pageItemDragOperationStartIndex + "->" + pageItemDragOperationFinalIndex)
        pageproxymodel.move(pageItemDragOperationStartIndex, pageItemDragOperationFinalIndex)
        pageItemDragOperationStartIndex = -1
        pageItemDragOperationFinalIndex = -1
    }
}

The c++ model's move function forwards the call to this handler:

bool PageModel::moveRow(const QModelIndex &sourceParent,
                        int sourceRow,
                        const QModelIndex &destinationParent,
                        int destinationChild)
{
    if ( sourceRow < 0 || sourceRow > rowCount()-1 ||
         destinationChild < 0 || destinationChild > rowCount() )
    {
        return false;
    }

    beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationChild);
 
    QList<QStandardItem*> rowItems = takeRow(sourceRow);
    insertRow(destinationChild, rowItems);

    endMoveRows();

    return true;
}

With the above c++ model code, it crashes at the release handler in QML:

enter image description here

I've tried other things to see the effect, no crashes, but also not the expected behaviour.

Basically all I want to do is to save the ListView state via the c++ model, after all that is a standard use case and something simple must be wrong on my side, yet I can't see it.

Upvotes: 0

Views: 569

Answers (2)

Stephen Quan
Stephen Quan

Reputation: 25871

One thing I like to do with DelegateModel makes use of DelegateModelGroup. By declaring a group named "all", it introduces an attached property allIndex which is useful for tracking an item after it has been reordered. The following example implements a DelegateModel with both MouseArea and DropArea. When in dragging mode, I disable all MouseArea so that the DropArea can have a chance at responding.

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

Page {
    property int activeMouseArea: -1
    ListView {
        id: listView
        width: 420
        height: parent.height
        model: SampleDelegateModel { }
        ScrollBar.vertical: ScrollBar {
            width: 20
            policy: ScrollBar.AlwaysOn
        }
    }
    footer: Text { id: dbg }
}

// SampleData.qml
import QtQuick
import QtQuick.Controls

ListModel {
    ListElement { name: "Steve Jobs" }
    ListElement { name: "Jeff Bezos" }
    ListElement { name: "Bill Gates" }
    ListElement { name: "Elon Musk" }
}

// SampleDelegateModel.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models

DelegateModel {
    id: delegateModel
    model: SampleData { }
    delegate: SampleDelegate { }
    groups: [
        DelegateModelGroup {
            id: allItems
            name: "all"
            includeByDefault: true
        }
    ]
    filterOnGroup: "all"
    function moveItem(from, to) {
        dbg.text = `Debugging: moveItem(${from},${to})`;
        allItems.move(from, to);
    }
}

// SampleDelegate.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models

Rectangle {
    property int allIndex: DelegateModel.allIndex
    width: 400
    height: labelText.height + 20
    border.color: "grey"
    z: mouseArea.drag.active || mouseArea.pressed ? 2 : 1
    property int dragTo: -1
    Drag.active: mouseArea.drag.active

    Text {
        id: labelText
        anchors.centerIn: parent
        text: allIndex + ": [" + index + "] " + name
    }

    DropArea {
        anchors.fill: parent
        onEntered: drag.source.dragTo = allIndex
    }

    MouseArea {
        id: mouseArea
        anchors.fill: parent
        drag.target: parent
        property point startPoint
        enabled: activeMouseArea === -1
        onPressed: {
            activeMouseArea = allIndex;
            dragTo = -1;
            startPoint = Qt.point(parent.x, parent.y);
        }
        onReleased: {
            activeMouseArea = -1;
            [parent.x,parent.y] = [startPoint.x, startPoint.y];
            Qt.callLater(delegateModel.moveItem, allIndex, dragTo);
        }
    }
}

You can Try it Online!

Upvotes: 1

user2366975
user2366975

Reputation: 4700

Found the mistake: The pageItemDragOperationStartIndex and pageItemDragOperationFinalIndex variables where part of each delegate, but not of the page. Also, as was pointed out in the comments, using a QStandardItemModel it is not necessary to call the begin/end functions. Now it works like a charm.

Upvotes: 0

Related Questions