Raymii.org
Quis custodiet ipsos custodes?Home | About | All pages | Cluster Status | RSS Feed
Qt/QML: Expose C++ classes to QML and why setContextProperty is not the best idea
Published: 03-10-2021 | Author: Remy van Elst | Text only version of this article
❗ This post is over three years old. It may no longer be up to date. Opinions may have changed.
Table of Contents
Qt/Qml traffic light example using different C++ integrations methods
In this article I'm going to discuss the different ways to expose a C++ class to QML. QML is a markup language (part of the QT framework) like HTML/CSS, with inline JavaScript that can interact with the C++ code of your (QT) application. There are multiple ways to expose a C++ class to QML, each with their own benefits and quirks. This guide will cover three integration methods, qmlRegisterSingletonType<>
, rootContext->setContextProperty()
and qmlRegisterType<>
. We'll end off with a simple benchmark showing the difference in startup times between the first two.
The executive summary is that setContextProperty
is deprecated, has a
performance impact (and you should use qmlRegisterSingletonType<>
. In my
benchmarks the qmlRegisterSingletonType
one is faster than
setContextProperty
. If you need more than one instance of your class, use
qmlRegisterType<>
and instantiate your objects in QML directly.
qmlRegisterType
is also faster than a context property in my benchmarks.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!
The singleton method is in my humble opinion the best method if you need one specific instance (like a model or a viewmodel) and the registerType method is the best method if you need to instantiate many things in QML. Setting a root context property has multiple issues, performance being one of them, as well as possible name clashes, no static analysis and it is available to anyone anywhere in QML. According to a Qt bug report (QTBUG-73064) it will be removed from QML in the future.
Introduction
Having clear boundaries in your application instead of an intertwined
mess where everything is tightly coupled to everything else is, in my
opinion, preferable. With a singleton or a type that separation is possible,
with a root context property that isn't possible. For small projects, the
setContextProperty
method is okay, but the singleton method is not more
effort, so even in that case I would prefer using singletons.
The Qt/QML documentation is comprehensive, but one flaw I find is that the
framework has no one (recommended) way of doing stuff. You can find all method
parameters and possible options, but if you want to know how to change the
colour of the text on a Button{}
, good luck searching on StackOverflow.
Same goes for integrating C++ with QML. The Qt documentation provides an
overview of different integration methods but does not tell you which one is
best. It just tells you what is possible and leaves it up to you to decide.
There is a flowcharts to help you which method to use, but almost all guides
and examples online just use rootContext->setContextProperty()
. Even my own
article on signals and slots uses that, due to the simplicity for small projects.
QML should not have any knowledge of the domain, it is just a UI markup
language, so any actual work or logic should be done on the C++ side, not via
QML/JavaScript. Using JavaScript gets messy very fast and is not testable via
unit tests, therefore using it is a big no no for me. Just as with WPF
and
XAML
on the Microsoft side, your user interface should have just a few
bindings to the viewModel
and no code or logic of its own. I've seen entire
state machines and complex JavaScript methods in QML that were so complex, I
still have nightmares from them. All of those functions could just be done in
C++, where they would be testable using unit tests. I bet you they would also
be faster.
The reason for writing this article is that I was diving in to the different
options on C++ integration in QML. At work we recently refactored a whole
bunch of QML code for performance reasons, dropping one global context
property helped immensely. I also namespaced much of our code and assets and
ran into more than one issue with missing or wrong Qt documentation. Our code
is compiled as a static application and as staticlib
in the case of
libraries, including all assets in a qrc
file. That static compilation and
filesystem paths that almost matched my qmldir
names (capital letter
mismatch) combined with wrong documentation gave many headaches, but in the
end I fixed it all up, showing a noticeable user-facing increase in response
times.
The example source code for this project can be found on my github here.
Traffic Light QML Example
The first iteration of my Qml Traffic Light
I've built a simple QML example with a traffic light and some buttons to
control said traffic light. The TrafficLightQml
object is a rectangle with
3 circles in it, each a different colour. Three properties are exposed to
turn the different lamps on or off. This is an opacity
controlled by a
bool
, to keep things simple. Not the best example, a statemachine would be
ideal for this, but to keep it simple for this article I decided that this
was just fine.
The TrafficLightQmlControlButtons
houses two buttons and exposes one property
and one signal. Actually two signals, since properties have an implicitly generated
onXXXChanged
signal. One button turn the light on or off and one button cycles
through the different lamps in the pattern the Dutch traffic lights use:
Red (stop) -> Green (go) -> Orange (caution, almost Red)
Why expose properties and signals instead of calling the relevant functions inside of the TrafficLight QML itself? That would tightly couple the QML control to the C++ counterpart and exposure method. By making the QML control generic enough, I can swap the implementation whenever I feel like. The user interface just needs to know how it looks and what do do, not how or when to do it. This makes unit testing the behaviour much easier, because there is no intelligence in the QML control, you don't have to test that. We should be able to trust that the framework works in passing signals and methods. The core logic, like what lamp pattern or when to turn on or off, should be unit tested, which is easy to do with for example Qt Test or GoogleTest. Testing a QML control / javascript function is much harder.
The main.qml
file has 4 instances of those two controls, but with each
one the properties and signals are bound to different C++ objects. That way
you can clearly see how to use each one including how they are created
and passed along in main.cpp
.
The file and class names are very verbose to show you what is used when
and where. If everything (qml, c++, id's) was named trafficlight
, that
visibility and insight is lost. Now it's very clear which line relates
to which component, both in QML as in C++.
setContextProperty
Lets start off with the most popular example, almost every tutorial you find
uses it. Even in the Qt official documentation on best practices, section
Pushing References to QML
, they use a setContextProperty
.
When using setContextProperty
, the property is available to every component
loaded by the QML engine. Context properties are useful for objects that must be
available as soon as the QML is loaded and cannot be instantiated in QML.
In my traffic light example it looks like this in main.cpp
TrafficLightClass trafficLightContext;
qmlRegisterUncreatableType<TrafficLightClass>("org.raymii.RoadObjectUncreatableType", 1, 0, "TrafficLightUncreatableType", "Only for enum access");
engine.rootContext()->setContextProperty("trafficLightContextProperty", &trafficLightContext);
In (every) QML I can use it like so:
Component.onCompleted: { trafficLightContextProperty.nextLamp(); // call a method }
redActive: trafficLightContextProperty.lamp === TrafficLightUncreatableType.Red // use a property
No import statement required. There is a paragraph regarding enums later on
in the article, which explains the UncreatebleType
you see above. You can skip
that part if you don't plan to use enums from your class on the QML side.
There is nothing inherently wrong for now with using this approach to get a C++ class in QML. For small projects or projects where performance is not an issue, the context property is just fine. In the grand scheme of things we're talking about the -ilities, like maintainability, but for a small project that probably does not matter as much as in a project with a larger codebase or multiple teams working on it.
Why is a context property bad then?
There are a few downsides compared to the singleton or registerType approach. There is a Qt Bug tracking the future removal of context properties, a StackOverflow post and a QML Coding Guide give a great summary. The QML documentation also notes these points, but in a less obvious way, so the summary is nice.
Quoting the Qt bug (QTBUG-73064):
The problem with context properties is that they "magically" inject state into your QML program. Your QML documents do not declare that they need this state, but they usually won't work without. Once the context properties are present, you can use them, but any tooling cannot properly track where they are added and where they are (or should be) removed. Context properties are invisible to QML tooling and the documents using them are impossible to validate statically.
Quoting the QML Coding guide:
Context properties always takes in a QVariant
or QObject
, which means that
whenever you access the property it is re-evaluated because in between each
access the property may be changed as setContextProperty()
can be used at
any moment in time.
Context properties are expensive to access, and hard to reason with. When you are writing QML code, you should strive to reduce the use of contextual variables (A variable that doesn't exist in the immediate scope, but the one above it.) and global state. Each QML document should be able to run with QML scene provided that the required properties are set.
Quoting this answer from StackOverflow regarding issues with setContextProperty
:
setContextProperty
sets the object as value of a property in the very root
node of your QML tree, so it basically looks like this:
property var myContextProperty: MySetContextObject {}
ApplicationWindow { ... }
This has various implications:
- You need to have cross-file references possible to files that are
not "local" to each other (
main.cpp
and wherever you try to use it) - Names are easily shadowed. If the name of the context property is used somewhere else, you will fail to resolve it.
- For name resolution, you crawl through a possible deep object tree, always looking for the property with your name, until it finally finds the context property in the very root. This might be a bit inefficient - but probably no big difference.
qmlRegisterSingletonType
on the other hand enables you to import the data at
the location where you need it. So you might benefit from faster name
resolution, shadowing of the names is basically impossible and you don't have
intransparent cross-file references.
Now that you've seen a bunch of reasons why you should almost never use a context property, let's continue on to how you should be exposing a single instance of a class to QML.
qmlRegisterSingletonType<>
A singleton type enables properties, signals and methods to be exposed in a
namespace without requiring the client to manually instantiate an object
instance. QObject
singleton types are an efficient and convenient way to
provide functionality or global property values. Once registered, a QObject
singleton type should be imported and used like any other QObject
instance
exposed to QML.
So, basically the same as the context property, except that you have to import it in QML. That, for me, is the most important reason to use singletons over context properties. In the earlier paragraphs I already stated differences and disadvantages of context properties, so I won't repeat myself here.
In the example traffic light code, this is the relevant code in main.cpp
:
TrafficLightClass trafficLightSingleton;
qmlRegisterSingletonType<TrafficLightClass>("org.raymii.RoadObjects", 1, 0, "TrafficLightSingleton",
[&](QQmlEngine *, QJSEngine *) -> QObject * {
return &trafficLightSingleton;
// the QML engine takes ownership of the singleton so you can also do:
// return new trafficLightClass;
});
On the QML side, you have to import the module before you can use it:
import org.raymii.RoadObjects 1.0
Usage example:
Component.onCompleted: { TrafficLightSingleton.nextLamp() // call a method }
redActive: TrafficLightSingleton.lamp === TrafficLightSingleton.Red; // use a property
No enum weirdness with UncreatableTypes
in this case.
qmlRegisterType
All previous paragraphs have exposed a single existing C++ object to QML. That
is fine most of the time, we at work expose our models
and viewmodels
this way to QML. But, what if you need to create and use more than one
instance of a C++ object in QML? In that case, you can expose the entire
class to QML via qmlRegisterType<>
, in our example in main.cpp
:
qmlRegisterType<TrafficLight>("org.raymii.RoadObjectType", 1, 0, "TrafficLightType");
On the QML side you again need to import it:
import org.raymii.RoadObjectType 1.0
Usage is like the other examples, with the addition of creating an instance of your object:
TrafficLightType {
id: trafficLightTypeInstance1
}
TrafficLightType {
id: trafficLightTypeInstance2
}
In the above example I've made 2 instances of that C++ type, in QML, without manually
creating one and exposing that instance in main.cpp
. Usage is almost the same as the
singleton:
redActive: trafficLightTypeInstance1.lamp === TrafficLightType.Red; // use a property
Component.onCompleted: { trafficLightTypeInstance1.nextLamp() // call a method }
And for our second instance:
redActive: trafficLightTypeInstance2.lamp === TrafficLightType.Red; // use a property
Component.onCompleted: { trafficLightTypeInstance2.nextLamp() // call a method }
The only difference is the ID, trafficLightTypeInstance1
vs trafficLightTypeInstance2
.
If you're going to have many things, exposing the entire class via qmlRegisterType
is
way more convenient than manually creating all of those things in C++, then exposing
them as singletons and finally importing them in QML.
Oddities with setContextProperty and enums
In the example traffic light class we have an enum class
for the
LampState
. The lamp can be Off
or any of the three colors. When
registering the type as a singleton, the following QML property assignment
via a boolean evaluation works:
redActive: TrafficLightSingleton.lamp === TrafficLightSingleton.Red
lamp
is an exposed Q_PROPERTY
with a signal attached on change. Red
is part of the enum class
.
However, when using the same property statement with the instance registered
via setContextProperty
, the following does not work:
redActive: trafficLightContextProperty.lamp === trafficLightContextProperty.Red
Results in a vague error like qrc:/main.qml:92: TypeError: Cannot read
property 'lamp' of null
and the property is never set to true. I've tried
many different solutions, like calling the getter function the QML signal
used (.getLamp()
) and debugging in Component.onCompleted()
. A
Q_INVOKABLE
debug method on the class does work fine, but the enum value
return undefined
. Other calls to slots, like .nextLamp()
work just fine,
only the enum values are not accessible.
This is listed on the flowchart and in the docs, but I bet you are frustrated before you've found that out.
Qt Creator is aware of the values, it even tries to auto-fill them, and the error messages are not helpful at all. Don't try to auto-fill them if I can use them or give a helpful error message, would be my suggestion to whomever develops Qt Creator.
The solution for this is, as listed in the docs, that is to register the
entire class as an UncreatableType
:
Sometimes a QObject-derived class may need to be registered with the QML
type system but not as an instantiable type. For example, this is the
case if a C++ class:
is an interface type that should not be instantiable
is a base class type that does not need to be exposed to QML
**declares some enum that should be accessible from QML, but otherwise should not be instantiable**
is a type that should be provided to QML through a singleton instance, and should not be instantiable from QML
Registering an uncreatable type allows you to use the enum values but you
cannot instantiate a TrafficLightType {}
QML Object. That also allows you
to provide a reason why the class is uncreatable, very handy for future
reference:
qmlRegisterUncreatableType<TrafficLight("org.raymii.RoadObjectType", 1, 0, "TrafficLightType", "Only for enum access");
In your QML file you now have to import the type:
import org.raymii.RoadObjectType 1.0
After which you can use the enum values in a comparison:
redActive: trafficLightContextProperty.lamp === TrafficLightType.Red
If you're putting in all that extra work to register the type, why not just
use the singleton implementation. If you're not using enums
you can get
away with setContextProperty()
, but still. Importing something only when
you need it instead of having it available everywhere anytime feels much
better to me.
Why not QML_ELEMENT
/ QML_UNCREATABLE
/ QML_INTERFACE
/ QML_SINGLETON
?
In Qt 5.15 a few new methods were made available to integrate C++ with QML. These
work with a macro in your header file and an extra definition in your .pro
file.
QML_ELEMENT / QML_UNCREATABLE / QML_INTERFACE / QML_SINGLETON / QML_ANONYMOUS
In the latest 5.15 doc snapshot and the blogpost these methods are explained, they should solve an issue that could arise, namely that you must keep your C++ code in sync with your QML registrations. Quoting the blogpost:
You always need to keep your type registrations in sync with the actual types. This is especially bothersome if you use revisions to make properties available in different versions of an import. Even if not, the fact that you need to specify the registration separately from the type is a burden as you can easily lose track of how you registered which types into which modules.
Then they go into some more (valid) technical details.
The reason I'm not including these in this comparison is because they are
new, only available in Qt 5.15 and later and because they depend on .pro
files and thus on qmake
. cmake support is not available, not even
in Qt 6.0.
If youre codebase is new enough to run on this latest Qt 5.15 version, or you're
running 6+, then these new methods are better than the ones listed above,
please refer to the technical part of the blogpost why. If you can,
thus if your Qt version and build system (qmake
) allows it, it's best
to use QML_SINGLETON
and friends.
I've written a small example to achieve the same as qmlRegisterType<>
below
for reference. In your .pro
file you add an extra CONFIG+=
parameter
(qmptypes
) and two other new parameters:
CONFIG += qmltypes
QML_IMPORT_NAME = org.raymii.RoadObjects
QML_IMPORT_MAJOR_VERSION = 1
In your .cpp
class, in our case, TrafficLightClass.h
, you add the following:
#include <QtQml>
[...]
// below Q_OBJECT
QML_ELEMENT
If you want the same effect as a qmlRegisterSingleton
, add QML_SINGLETON
below the QML_ELEMENT
line. It creates a default constructed singleton.
In your QML file, import the registered type:
import org.raymii.RoadObjects 1.0
You can then use them in QML, by their class name (not a seperate name as we did above):
TrafficLightClass {
[...]
}
Bechmarking startup time
To be sure if what we're doing actually makes any difference I've made up a simple benchmark. The only way to make sure that something is faster is to profile it. The Qt Profiler is in a whole league of its own, so I'm going to use a simpler test.
Even if the singleton variant turns out to be slower, I would still prefer it over the global property for the same reasons as stated earlier. (If you're wondering, I've written this section before doing the benchmarks.)
The first line in main.cpp
prints out the current epoch in milliseconds and
on the QML side in the root window I've added an Component.onCompleted
handler that also prints out the current epoch in milliseconds, then calls
Qt.Quit
to exit the application. Subtracting those two epoch timestamps
gives me startup runtime, do that a few times and take the average, for the
version with only a qmlRegisterSingleton
and the version with only a
rootContext->setProperty()
.
The build has the Qt Quick compiler enabled and is a release build. No other
QML components were loaded, no exit button, no help text, just a window with
a TrafficLightQML
and the buttons. The traffic light QML has an onCompleted
that turns the C++ light on.
Do note that this benchmark is just an indication. If you have application performance issues I recommend you to use the Qt Profiler to figure out what is going on. Qt has an article on performance that can also help you.
Printing the epoch timestamp in main.cpp
:
#include <iostream>
#include <QDateTime>
[...]
std::cout << QDateTime::currentMSecsSinceEpoch() << std::endl;
Printing it in main.qml
:
Window {
[...]
Component.onCompleted: {
console.log(Date.now())
}
}
Using grep
and a regex to only get the timestamp, then reversing it with
tac
(reverse cat
), then using awk
to subtract the two numbers. Repeat
that five times and use awk
again to get the average time in milliseconds:
for i in $(seq 1 5); do
/home/remy/tmp/build-exposeExample-Desktop-Release/exposeExample 2>&1 | \
grep -oE "[0-9]{13}" | \
tac | \
awk 'NR==1 { s = $1; next } { s -= $1 } END { print s }';
done | \
awk '{ total += $1; count++ } END { print total/count }'
The average for the
qmlRegisterSingleton<>
example: 420 msThe average for the
qmlRegisterType<>
example: 492.6 msThe average for the
rootContext->setContextProperty
example: 582.8 ms
Looping the above benchmark 5 times and averaging out those averages results in 439.88 ms for the singleton, 471.68 ms for the registerType and 572.28 ms for the rootContext property.
This simple example already shows a difference of 130 to 160 ms for a singleton variable. Even registering a type and instantiating it in QML is faster than a context property. (Didn't expect such a difference actually)
This benchmark was done on a Raspberry Pi 4, Qt 5.15 and while this was running no other applications except for IceWM (window manager) and xterm (terminal emulator) were running.
I repeated this process with our work application, which has quite a large and complex object with about a megazillion property bindings (actual number, counted them myself when refactoring) and there the difference was more than 2 seconds.
Please though, do a few benchmarks yourself on your own machine with your own code before you take the above measurements as absolute source of truth.
And if you know an easy way to measure startup time with the Qt Profiler a few times and averaging it out, easier than manually digging through the entire list, send me an email.
Tags: articles , c++ , cpp , debugging , development , javascript , qml , qt , qt5 , singleton