Soroush Shariaty
Soroush Shariaty

Reputation: 15

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:

Upvotes: 0

Views: 307

Answers (1)

smr
smr

Reputation: 1004

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:

Upvotes: 2

Related Questions