ImaginativeThinking.ca


A developers blog

How to make a QML Component a Singleton?

By: Brad

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.

MyStyleObject.qml
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.

MyStyleObject.qml
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("file:///absolute/path/MyStyleObject.qml"), "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()).

main.qml
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.

main.qml
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.

QMLSingletonSample_1

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.

QMLProfiler_1

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.

QMLProfiler_2

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

Brad

My interest in computer programming started back in high school and Software Development has remained a hobby of mine ever since. I graduated as a Computer Engineering Technologist and have been working as a Software Developer for many years. I believe that software is crafted; understanding that how it is done is as important as getting it done. I enjoy the aesthetics in crafting elegant solutions to complex problems and revel in the knowledge that my code is maintainable and thus, will have longevity. I hold the designation Certified Technician (C.Tech.) with the Ontario Association of Computer Engineering Technicians and Technologists (OACETT), have been certified as a Professional Scrum Master level 1 (PSM I) and as a Professional Scrum Developer level 1 (PSD I) by Scrum.org as well as designated as an Officially Certified Qt Developer by the Qt Company. For more on my story check out the about page here

Feel free to write a reply or comment.

  • 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:

      Text { color: myStyle.colourWhite; }

      we would write

      Text { color: MyStyle.colourWhite; }

      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

  • 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.