Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up
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.
About KDAB
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Upgrade your applications from Qt 5 to Qt 6 with KDAB’s migration services. Get a free migration assessment and join a hands-on workshop to prepare your team for a successful transition!
Learn more
2 Comments
11 - Apr - 2024
Robert
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++
11 - Apr - 2024
Javier Cordero
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++.