Search code examples
qtqmlqtquick2loader

QML DelegateModel: Access DelegateModel from delegate


I'm trying to create a ListView with different delegates and a drag'n'drop functionality. The delegates shall be loaded with a Loader.

The QML Documentation provides a working example for a ListView without a Loader: http://doc.qt.io/qt-5/qtquick-tutorials-dynamicview-dynamicview3-example.html

However, using the Loader I get the error: Cannot read property 'DelegateModel' of undefined

I do not understand how I can access the DelegateModel from the Loader.

A hint to the solution is highly appreciated!

main.qml:

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.1
import QtQml.Models 2.3

Window {
    id: mainroot

    visible: true
    width: 640
    height: 480

    Rectangle{
        id:viewContainer

        anchors.fill: parent

        DelegateModel {
            id: visualModel

            model: ListModel{
                id:m_model

                ListElement{
                    type:1
                    m_text :"Text1"
                }

                ListElement{
                    type:1
                    m_text :"Text2"
                }
            }

            delegate:        Loader{
                id:idLoader

                width: view.width
                height: childrenRect.height

                Component.onCompleted: {
                    switch(type){
                    case 1:
                        idLoader.setSource("TestDelegate.qml", {"m_text": m_text})
                        break;
                    }
                }
            }
        }

        ListView{
            id: view

            anchors.fill: parent
            spacing: 5

            model: visualModel
        }
    }
}

TestDelegate.qml:

    import QtQuick 2.7

MouseArea {
    id: dragArea

    property bool held: false
    property string m_text

    anchors { left: parent.left; right: parent.right }
    height: 50
    width: view.width

    drag.target: held ? content : undefined
    drag.axis: Drag.YAxis

    onPressAndHold: held = true
    onReleased: held = false

    Rectangle {
        id: content

        anchors {
            horizontalCenter: parent.horizontalCenter
            verticalCenter: parent.verticalCenter
        }
        width: dragArea.width
        height: textfield.implicitHeight


        Drag.active: dragArea.held
        Drag.source: dragArea
        Drag.hotSpot.x: width / 2
        Drag.hotSpot.y: height / 2

        border.width: 1
        border.color: "lightsteelblue"
        color: dragArea.held ? "lightsteelblue" : "white"
        Behavior on color { ColorAnimation { duration: 100 } }
        radius: 2
        states: State {
            when: dragArea.held

            ParentChange { target: content; parent: viewContainer }
            AnchorChanges {
                target: content
                anchors { horizontalCenter: undefined; verticalCenter: undefined }
            }
        }

        Text{
            id: textfield

            anchors.centerIn: parent
            text: m_text
        }

    }
    DropArea {
        anchors { fill: parent; margins: 10 }

        onEntered: {
            visualModel.items.move(
                    idLoader.item.drag.source.DelegateModel.itemsIndex,
                    idLoader.item.dragArea.DelegateModel.itemsIndex)
        }
    }
}

Solution

  • The items defined in the file loaded with Loader or in general with any other .qml file that is imported should not depend directly on the main file since the ids have a scope, it is better to expose properties, in your case:

    ╭------------------------------------------╮
    |                   bool held        ------┿--->
    |  TestDelegate     string m_text    ------┿--->
    |  ============     DelegateModel md ------┿--->
    |                   int index        ------┿--->
    ╰------------------------------------------╯
    

    Considering the above, the solution is the following:

    main.qml

    import QtQuick 2.7
    import QtQuick.Window 2.0
    import QtQml.Models 2.3
    
    Window {
        id: mainroot
        visible: true
        width: 640
        height: 480
    
        Rectangle{
            id:viewContainer
            anchors.fill: parent
    
            DelegateModel {
                id: visualModel
                model: ListModel{
                    id:m_model
                    Component.onCompleted: {
                        for(var i=0; i< 40; i++){
                            m_model.append({"type": 1, "m_text": "Text" + i})
                        }
                    }
                }
                delegate:
                    Loader{
                    id: idLoader
                    width: view.width
                    height: childrenRect.height
                    property int index: DelegateModel.itemsIndex
                    onIndexChanged: if(status == Loader.Ready) idLoader.item.index = index
                    Component.onCompleted: {
                        switch(type){
                        case 1:
                            idLoader.setSource("TestDelegate.qml", {
                                                   "m_text": m_text,
                                                   "index": index,
                                                   "md": visualModel
                                               })
                            break;
                        }
                    }
                }
            }
            ListView{
                id: view
                anchors.fill: parent
                spacing: 5
                model: visualModel
            }
        }
    }
    

    TestDelegate.qml

    import QtQuick 2.7
    import QtQml.Models 2.3
    
    MouseArea {
        id: dragArea
        property bool held: false
        property string m_text
        property DelegateModel md: null
        property int index : -1;
        anchors { left: parent.left; right: parent.right }
        height: 50
        width: view.width
        drag.target: held ? content : undefined
        drag.axis: Drag.YAxis
        onPressAndHold: held = true
        onReleased: held = false
        Rectangle {
            id: content
            anchors {
                horizontalCenter: parent.horizontalCenter
                verticalCenter: parent.verticalCenter
            }
            width: dragArea.width
            height: textfield.implicitHeight
            Drag.active: dragArea.held
            Drag.source: dragArea
            Drag.hotSpot.x: width / 2
            Drag.hotSpot.y: height / 2
            border.width: 1
            border.color: "lightsteelblue"
            color: dragArea.held ? "lightsteelblue" : "white"
            Behavior on color { ColorAnimation { duration: 100 } }
            radius: 2
            states: State {
                when: dragArea.held
                ParentChange { target: content; parent: viewContainer }
                AnchorChanges {
                    target: content
                    anchors { horizontalCenter: undefined; verticalCenter: undefined }
                }
            }
            Text{
                id: textfield
                anchors.centerIn: parent
                text: m_text
            }
        }
        DropArea {
            anchors { fill: parent; margins: 10 }
            onEntered: {
                if(md !== null)
                    md.items.move(drag.source.index, dragArea.index)
            }
        }
    }