How to make a QML Component a Singleton?
In this tutorial I’ll walk you through how to define a custom QML Component as a singleton.
UPDATE: I uploaded a new sample code which better outlines the difference between using an instance of
MyStyleObject and using the singleton
MySingletonStyleObject.You can download it here.
Currently I’m working on a rather large Qt Quick application which will be deployed on an embedded device. I’m starting to notice performance issues as we add more and more screens to the application. I decided to run the QML Profiler that is part of Qt Creator and one of the things I noticed was that one of our QML Components is being created 1500+ times. This component is an Item which holds a number of properties defining different styling options like what the colour Blue is for our purposes; its our style sheet. The issue being that its loading non-standard fonts which is expensive so as it turns out we’re loading the fonts 1500+ times.
Each screen/page in the application is instantiating its own copy of the style object as we weren’t really keen on defining one globally and simply referring to it. I mean really there is nothing wrong with that approach per-say but it does relay on a dependency; that someone instantiated the object somewhere in the application and that the objects id never changes.
So how can I reduce the number of instantiated objects without depending on a locked in stone id name?
Enter the Singleton
So as it turns out Qt Quick does have a mechanism to let you define a QML component as a singleton. I didn’t even think to see if that was even an option, a college mentioned it to me in passing and they only knew about it as a one line statement on a blog post.
Ok so its a thing but how do you do it?
There are three steps you need to do; first you need to use the
pragma Singleton keyword in your QML script, then you need to register the component as a singleton, and lastly you need to import it into your other QML scripts which are going to use it.
Step 1: Declaring the QML Component as a Singleton
Lets say this is your style sheet QML object that you want to make a singleton.
import QtQuick 2.0 Item { readonly property string colourBlue: "blue" readonly property string colourRed: "red" readonly property int fontPointSize: 16 }
To declare it as a singleton you need to add the keywords
pragma Singleton to the top of the script file.
pragma Singleton import QtQuick 2.0 Item { readonly property string colourBlue: "blue" readonly property string colourRed: "red" readonly property int fontPointSize: 16 }
Step 2: Registering the Component
Now you have two options you can either register the Component in C++ or by using a qmldir file.
To register the singleton via C++ somewhere in your C++ code you need to call
qmlRegisterSingletonType().
#include <QtQml> ... qmlRegisterSingletonType( QUrl(""), "ca.imaginativethinking.tutorial.style", 1, 0, "MyStyle" ); ...
If adding the call to
qmlRegisterSingletonType() won’t work for you, maybe this is a Qt Quick UI project (i.e. no C++ ) then you can add a file called qmldir to the directory where your MyStyleObject.qml file exists. When importing a directory the QML Engine first looks for the qmldir file and uses it to import the scripts found in that directory; if the file does not exist it will import the scripts found with default values (i.e. non-singletons and uses the file name as the component name). The qmldir file can define different names to be used instead of the file name and can also tell the import to register the script as a singleton.
Here is what the directory structure should look like:
/root + absolute | + path | | + qmldir | | + MyStyleObject.qml | | + AnotherObject.qml | | + MyButton.qml | | + MySwitch.qml | + main.qml
Here is how to define the qmldir file:
singleton MyStyle 1.0 MyStyleObject.qml MyOtherObject 1.0 AnotherObject.qml MyButton 1.0 MyButton.qml
Step 3: Importing and Using the Singleton
If you use the C++ option above then in order to import and use the singleton in your QML script you need to import the module you defined via the second parameter of
qmlRegisterSingletonType() then access the object using the registered name (parameter three of
qmlRegisterSingletonType()).
import QtQuick 2.0 import ca.imaginativethinking.tutorial.style 1.0 Rectangle { anchors.fill: parent color: MyStyle.colourBlue // <-- Notice that to access the singleton I use the object name not id (i.e. Capital M) }
If you used the qmldir approach then you simply need to import the directory which will register all the scripts under that directory.
import QtQuick 2.0 import "path" Rectangle { anchors.fill: parent color: MyStyle.colourBlue // <-- Notice that to access the singleton I use the object name not id (i.e. Capital M) }
Note here however that if main.qml was within the path directory you would still have to import the path as it does not appear that the qmldir file gets used when you relay on the auto look up path.
Commercial break...
So what does this buy us?
Example time: lets say we create an app that shows 100 blue boxes with red boarders on the screen. We'll use the
MyStyleObject component to hold what shade of blue Blue is and the thickness of the boarder.
How we'll achieve this is by using a
GridView with a integer model of 100 and the
MyStyleObject will be created within the delegate.
GridView { anchors.fill: parent model: 100 delegate: gridDelegate cellHeight: 50 cellWidth: cellHeight } Component { id: gridDelegate Rectangle { width: 50 height: width color: myStyle.colourBlue border.color: myStyle.colourRed border.width: myStyle.borderSize Text { anchors.centerIn: parent text: index font.pointSize: myStyle.fontPointSize color: myStyle.colourWhite } MyStyleObject { id: myStyle } } }
We can run the QML Profiler in Qt Creator (Analyze -> QML Profiler) it will switch Qt Creator over to the Analyze view and launch our application.
Once our application has launched we can press the stop button on the Analyze view to terminate the application and stop the profiler. It will then load the data into the view.
By instantiating an instance of the
MyStyleObject within the delegate you can see it gets created 100 times which takes 12.084ms; binded to 99 times which takes 1.130ms and that the compile phase for the object (which only happens once) takes 708.820µs.
If we change this into a singleton and run the same profiler we'll find that now the object is only created once (474.917µs); although the compile phase does take longer (1.917ms) your still saving about 10ms. This might not seem like a lot over all but when your trying to keep the presentation of your application at 60FPS every little bit helps.
GridView { anchors.fill: parent model: 100 delegate: gridDelegate cellHeight: 50 cellWidth: cellHeight } Component { id: gridDelegate Rectangle { width: 50 height: width color: MyStyle.colourBlue // <-- Capital M MyStyle means I'm now access the one and only instance of the MySingletonStyle object border.color: MyStyle.colourRed border.width: MyStyle.borderSize Text { anchors.centerIn: parent text: index font.pointSize: MyStyle.fontPointSize color: MyStyle.colourWhite } } }
So there you go that is all you have to do to define a QML Component as a singleton so that it only gets created once.
You can download a sample application which illustrate the above here:
QML Singleton Sample 1.1 - Updated to be more clear.
Thank you I hope you have enjoyed and found this tutorial helpful. Feel free to leave any comments or questions you might have below and I’ll try to answer them as time permits.
Until next time think imaginatively and design creatively
Hello, I don’t understand very clearly this situation. Singleton component can only be created once. So if we replace MyStyleObject with MySingleTonStyle component , the application is failed because there are more than one delegate is created. So you want to use your example to demonstrate the creation of SIngleton component? If we move MyStyleObject outsite of delegate declaration there are juts one creation for itself
Did I understand your purpose ?
Thanks
Thank you for your comment Tan Do.
Yes after looking at this post and the sample app again I fear I was not being clear enough; sorry about that.
When switching to use the MySingletonStyleObject object the creation of the MyStyleObject within the delegate is not changed to MySingletonStyleObject (which will fail as you found out) but is actually removed as the instantiation of the singleton object will be done the first time you use it. All uses of the id reference myStyle within the delegate are changed to MyStyle (notice the capital M) which is what we registered the MySingletonStyleObject as in the qmldir file.
So instead of:
we would write
I’ve updated the post and the sample app to show this more clearly.
Again thank you for your comment,
Until next time think imaginatively and design creatively
It’s great, Brad! Your blog help me so much when investigating Qt-Qml! Keep up the good job! 🙂
Hi,
Thank u for the Post.
I am developing with QT 5.4.1 QML/JS
I’m not being able to import the singleton into mi qml script (Principal.qml)
QML module not found.
My Steps:
1) I create MySingleton.qml file:
pragma Singleton
import QtQuick 2.0
Item {
property var _driver;
property string _codeapp;
function getDriver(){
return _driver;
}
function setDriver(driver){
_driver=driver;
}
function getCodeApp(){
return _codeapp;
}
function setCodeApp(codeapp){
_codeapp=codeapp;
}
}
2)
I created the qmldir (Add new file -> General -> Empty File) and add the following line
singleton MySingleton 1.0 MySingleton.qml
Principal.qml, MySingleton.qml and qmldir are placed in the same directory
Can u help me? Thank u in advance.
Hi,
I could do it looking in this post too. Thank u