Skip to content

Recursive Instantiation with Qt Quick and JSON Factory Design Techniques - Part 1

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.

Categories: KDAB Blogs / KDAB on Qt / QML / Qt / Technical

Tags: / /

2 thoughts on “Recursive Instantiation with Qt Quick and JSON”

  1. Quite interesting.
    Seeing all that switches, I wonder how the code would scale.
    My first thought was that doing it via QQmlComponent::create on the C++ side would be more efficient, and also allow you to use a more elegant code design in assigning properties using the created component’s QObject property interface.
    But I guess there is simply no public API to create many standard QML components like ‘Text’ via C++

    1. Certainly. You could also implement a function or binding for each Component in QML, that gets set from onItemChanged, allowing the components to assign their own values. This would be even closer to the original factory methods pattern. The video being referenced at the end of this article shows a different approach, where the properties are dynamically acquired from C++.

Leave a Reply

Your email address will not be published. Required fields are marked *