Search code examples
qtrecursionqmlinstantiationqt6

Is there a workaround for "recursive instantiation error" in Qml?


I have a situation which I need a component which holds a ListView of its own type. Here is a simplified version:

myComp.qml

import QtQuick

Item {
    Text {
        id: name
        text: qsTr("Hello")
    }

    ListView{
        model: 5
        delegate: MyComp{}
    }
}

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
    MyComp{

    }
}

qmldir.txt

MyComp 1.0 myComp.qml

After running this, I get recursive instantiation error which is expected.

My main questions are:

  • Is there a workaround to overcome this issue?
  • What is the general approach if you want to implement such thing? for example what should I do if I want to implement something like the query builder UI provided here?

Solution

  • As @Atmo mentioned in the comments, you cannot declare a type in itself. However, in your case, an infinite loop will not occur.

    As far as I know, there are two options for solving your problem:

    1. Use a Loader item, as mentioned in [1].
    2. Set the delegate during component declaration.

    1. Loader

    The simplest option is to use a Loader [1]. I also suggest using DelegateChooser to switch between delegates, as you don't need to use a Loader on all sub-items and write conditions, etc.

    For example:

    Group.qml

    import QtQuick
    import QtQuick.Controls
    import Qt.labs.qmlmodels
    
    Control {
        id: control
    
        property alias model: listview.model
        padding: 5
    
        DelegateChooser {
            id: chooser
            role: 'type'
    
            DelegateChoice {
                roleValue: 'data'
                Control {
                    required property var modelData
                    padding: 5
    
                    contentItem: Row {
                        spacing: 5
    
                        Repeater {
                            model: modelData.data
                            Label {
                                required property string modelData
                                padding: 5
                                width: 35
                                text: modelData
                                background: Rectangle {
                                    radius: 2; border { width: 1; color: '#ccc'}
                                }
                            }
                        }
                    }
    
                    background: Rectangle {
                        radius: 5
                        border { width: 1; color: '#eee'}
                    }
                }
            }
    
            DelegateChoice {
                roleValue: 'group'
                Loader {
                    required property var modelData
                    source: 'Group.qml'
                    width: control.availableWidth
                    onLoaded: item.model = modelData.data
                }
            }
        }
    
        contentItem: ListView {
            id: listview
            implicitHeight: contentHeight
            spacing: 5
            delegate: chooser
        }
    
        background: Rectangle {
            color: '#aafbf3dd'
            radius: 5; border { width: 1; color: '#dcc896'}
        }
    }
    

    And usage is like:

    Group {
        anchors.fill: parent
        model: [
            {type: 'data', data: [1,2,3]},
            {
                type: 'group',
                data: [
                    {type: 'data', data: [4,5,6,7]},
                    {type: 'data', data: [8,9,10,11]},
                    {type: 'group', data: [{type: 'data', data: [12,13,14]}]},
                ]
            },
        ]
    }
    

    Preview:

    2. Move Delegate to outside of file

    This is a cleaner approach, as it doesn't require a Loader, but it does require using multiple files, which is fine. Here is a simplified example:

    Group.qml

    import QtQuick
    import QtQuick.Controls
    
    Control {
        id: control
    
        property alias model: listview.model
        padding: 5
    
        property Component delegate: Item {}
    
        contentItem: ListView {
            id: listview
            spacing: 5
    
            implicitHeight: contentHeight
            delegate: control.delegate
        }
    
        background: Rectangle {
            color: '#aafbf3dd'
            radius: 5; border { width: 1; color: '#dcc896'}
        }
    }
    

    Delegate.qml (simplified version)

    import QtQuick
    import QtQuick.Controls
    import Qt.labs.qmlmodels
    
    DelegateChooser {
        id: chooser
        role: 'type'
    
        DelegateChoice {
            roleValue: 'data'
            Row {
                required property var modelData
                spacing: 5
                Repeater {
                    model: modelData.data
                    Label {
                        required property string modelData
                        text: modelData
                    }
                }
            }
        }
    
        DelegateChoice {
            roleValue: 'group'
            Group {
                required property var modelData
                width: ListView.view.parent.width - 10
                model: modelData.data
                delegate: chooser
            }
        }
    }
    

    The usage remains the same:

    Group {
        anchors.fill: parent
        model: [ /* same data */ ]
        delegate: Delegate {}
    }
    

    Preview: