Skip to content

Recursive Instantiation with Qt Quick and JSON

Thursday, 11 April 2024 | KDAB on Qt


Recently I was tasked to come up with an architecture for remote real time instantiation and updating of arbitrary QML components.

This entry shows how you can use a simple variation of the factory method pattern in QML for instantiating arbitrary components. I’ve split my findings into 3 blog entries, each one covering a slightly different topic. Part 1 focuses on the software design pattern used to dynamically instantiate components. Part 2 shows how to layout these dynamic components by incorporating QML’ s positioning and layout APIs. The last entry, consisting of Parts 3 and 4, addresses the anchors API and important safety aspects.

This is Part 1: Recursive Instantiation with Qt Quick and JSON.

The original factory method pattern made use of static methods to programmatically instantiate objects of different classes, instead of having to call their constructors. It achieved that by having the classes share a common ancestor. Our variation of the popular pattern uses a Loader to choose which component to load, and a Repeater to dynamically instantiate arbitrary instances of this loader using a model.

Here we specify which components with a JSON array and use a Repeater to load them.

    id: root
    // A JSON representation of a QML layout:
    property var factoryModel: [
        {
            "component": "Button",
        },
        {
            "component": "Button",
        }
    ]
    // Root of our component factory
    Repeater {
        model: root.factoryModel
        delegate: loaderComp
    }

To be able to instantiate any kind of item, you can use a Component with a Loader inside, as the Repeater’s delegate. This allows you to load a different component based on the Repeater’s model data.

    // Root component of the factory and nodes
    Component {
        id: loaderComp
        Loader {
            id: instantiator
            required property var modelData
            sourceComponent: switch (modelData.component) {
                case "Button":
                return buttonComp;
                case "RowLayout":
                return rowLayoutComp;
                case "Item":
                default: return itemComp;
            }
        }
    }

To assign values from the model to the component, add a method that gets called when the Loader’s onItemChanged event is triggered. I use this method to take care of anything that involves the component’s properties:

    // Root component of the factory and nodes
    Component {
        id: loaderComp
        Loader {
            id: instantiator
            required property var modelData
            sourceComponent: switch (modelData.component) {
                case "Button":
                return buttonComp;
                case "RowLayout":
                return rowLayoutComp;
                case "Item":
                default: return itemComp;
            }
            onItemChanged: {
                // Pass children (see explanation below)
                if (typeof(modelData.children) === "object")
                    item.model = modelData.children;

                // Button properties
                switch (modelData.component) {
                    case "Button":
                    // If the model contains certain value, we may assign it:
                    if (typeof(modelData.text) !== "undefined")
                        item.text = modelData.text;
                    break;
                }

                // Item properties
                // Since Item is the parent of all repeatable, we don't need to check
                // if the component supports Item properties before we assign them:
                if (typeof(modelData.x) !== "undefined")
                    loaderComp.x = Number(modelData.x);
                if (typeof(modelData.y) !== "undefined")
                    loaderComp.y = Number(modelData.y);
                // ...
            }
        }
    }

Examples of components that loaderComp could load are defined below. To enable recursion, these components must contain a Repeater that instantiates children components, with loaderComp set as the delegate:

    Component {
        id: itemComp
        Item {
            property alias children: itemRepeater.model
            children: Repeater {
                id: itemRepeater
                delegate: loaderComp
            }
        }
    }
    Component {
        id: buttonComp
        Button {
            property alias children: itemRepeater.model
            children: Repeater {
                id: itemRepeater
                delegate: loaderComp
            }
        }
    }
    Component {
        id: rowLayoutComp
        RowLayout {
            property alias children: itemRepeater.model
            children: Repeater {
                id: itemRepeater
                delegate: loaderComp
            }
        }
    }

The Repeater inside of the components allows us to instantiate components recursively, by having a branch or more of children components in the model, like so:

    // This model lays out buttons vertically
    property var factoryModel: [
        {
            "component": "RowLayout",
            "children": [
                {
                    "component": "Button",
                    "text": "Button 1"
                },
                {
                    "component": "Button",
                    "text": "Button 2"
                }
            ]
        }
    ]

Here we’ve seen how we can use a Repeater, a JSON model, a Loader delegate, and simple recursive definition to instantiate arbitrary QML objects from a JSON description. In my next entry I will focus on how you can lay out these arbitrarily instantiated objects on your screen.

Thanks to Kevin Krammer and Jan Marker whose insights helped improve the code you’ve seen here.

I hope you’ve found this useful! Part 2 may be found already or later by following this link.

Reference

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Recursive Instantiation with Qt Quick and JSON appeared first on KDAB.