Qt Graphs vs Qt Charts. Objective Take on the New Data Visualization Solution
The Qt Graphs is a powerful module of the Qt framework that enables developers to create interactive, both static and […]
This tutorial about how to expose object and register C++ class to QML is the first post in the new post series on Scythe Studio blog. The series is going to be entitled ‘How to integrate QML and C++’. With the new Qt 6 version it’s not that clear how to properly and easily implement this crucial mechanism in Qt software development.
Especially as many people currently move to CMake from qmake. Therefore, we thought that it would be a nice opportunity to explain various ways of QML and C++ integration in details.
Future posts will cover mechanisms like models and plugins, but for now let’s focus on the basics and let’s explain how to access C++ object from QML and how to register C++ class to QML.
There is no doubt that QML is a pretty, nice and even seductive comparing it to other programming languages including C++. Plenty of features common in today’s applications could be implemented using QML only.
For network communication over HTTP you can use XmlHttpRequest using JavaScript and there are QML items such as ListModel to keep your data in.
That may be tempting to stick with QML , especially for new Qt quick developers. However, after some time application written only in QML, would face maintenance problems.
So what, in short words, are the benefits of integrating QML and C++?
The first thing to learn is how to access C++ object from QML. When would you need that? For example, when you have logic implemented in some C++ class, but you want to can access properties, methods or signals of that class’s instance in QML. That’s the extremely popular case for Qt applications development.
Let’s start by learning how to access property of C++ class in QML . First you need to create a new class that will extend QObject class and use Q_OBJECT macro.
For this tutorial let’s create a class AppManager that would be intended to be a center of events in our app.
The class would keep an information about either the app has the dark mode on or not. For that purpose let’s add a property using Q_PROPERTY (type name READ getFunction WRITE setFunction NOTIFY notifySignal) macro.
The standard use of this macro assumes passing type of the property and names of the getter, setter and signal that must be implemented in the class. Therefore, in this class property declaration may look like this:
Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged)
All you need to do now is to add actual methods and signals to the class definition. Thankfully, you do not need to do that on your own. Just put your cursor on the property and hit Alt + Enter on your keyboard to trigger the refactor menu. There you should see an option to automatically generate missing property members.
Tip: You can find more useful keyboard shortcuts in our Qt Creator Cheat Sheet
After fixing some formatting issues AppManager class should look like this:
#ifndef APPMANAGER_H #define APPMANAGER_H #include <QObject> class AppManager : public QObject { Q_OBJECT Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged) public: explicit AppManager(QObject *parent = nullptr); bool isNightMode() const; void setIsNightMode(bool isNightMode); signals: void isNightModeChanged(); private: bool m_isNightMode = false; }; #endif // APPMANAGER_H
Then instantiate your class object and expose it in main.cpp file (or wherever you have access to QML engine) using QQmlContext::setContextProperty(const QString &name, QObject *value) method on the engine’s root context. You must pass a name under which the object will be accessible and the pointer to a QML object. This pair is the so-called context property. The main.cpp file should look more or less like this:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <AppManager.h> int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // exposing C++ object to Qml AppManager *appManager = new AppManager(&app); engine.rootContext()->setContextProperty("appManager", appManager); const QUrl url(u"qrc:/testapp/main.qml"_qs); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
And that would be it. All you need to do is execute setContextProperty() method. From this point, your C++ object is accessible from QML. As an example, we will simply run QML code which will change application’s theme depending on appManager’s isNightMode property value. The app will also have a button to allow users to change the property’s value.
import QtQuick import QtQuick.Controls Window { id: root readonly property color darkColor: "#218165" readonly property color lightColor: "#EBEBEB" width: 280 height: 150 visible: true title: qsTr("Expose C++ object test") color: appManager.isNightMode ? root.darkColor : root.lightColor Column { anchors.centerIn: parent spacing: 20 Text { color: appManager.isNightMode ? root.lightColor : root.darkColor text: qsTr("Is night mode on? - ") + appManager.isNightMode } Button { anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Change mode") palette.buttonText: appManager.isNightMode ? root.lightColor : root.darkColor // change isNightMode on clicked onClicked: { appManager.isNightMode = !appManager.isNightMode } } } }
As you can see, the property of C++ object is accessible for both reading and writing. The colors and texts are adjusted automatically thanks to the isNightModeChanged() signal used in Q_PROPERTY macro. That’s how the application behaves like when it started:
In order to be able to run a method on a C++ object, you need to inform the meta-object system about the fact that this method exists. You can do that either by putting the method declaration under public slots scope or by marking the method with Q_INVOKABLE macro. The signal needs to be under signals scope in class definition.
With this knowledge, let’s assume that we want to delegate some time-consuming and probably also intensive data operation from QML to C++. In order to do that, we will add performOperation method and operationFinished(const QString &operationResult) signal. The method will wait 5 seconds to mimic time-consuming operation and then emit a signal with random result.
The class should look then like this:
#ifndef APPMANAGER_H #define APPMANAGER_H #include <QObject> class AppManager : public QObject { Q_OBJECT Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged) public: explicit AppManager(QObject *parent = nullptr); bool isNightMode() const; void setIsNightMode(bool isNightMode); public slots: void performOperation(); signals: void isNightModeChanged(); void operationFinished(const QString &operationResult); private: bool m_isNightMode = false; }; #endif // APPMANAGER_H
That’s important to inform the meta-object system about the performOperation method. Otherwise you will get such an error when trying to call the method from QML:
TypeError: Property ‚performOperation’ of object AppManager(0x6000027f8ca0) is not a function
That’s what the method itself could look like:
void AppManager::performOperation() { QTimer *timer = new QTimer(this); timer->setSingleShot(true); connect(timer, &QTimer::timeout, this, [this]() { const int result = QRandomGenerator::global()->generate(); const QString &operationResult = result % 2 == 0 ? "success" : "error"; emit operationFinished(operationResult); }); timer->start(5000); }
At this point, you should be able to access the C++ class method and signal from QML. Let’s do some QML coding then.
import QtQuick import QtQuick.Controls Window { id: root readonly property color darkColor: "#218165" readonly property color lightColor: "#EBEBEB" width: 280 height: 150 visible: true title: qsTr("Expose C++ object test") color: root.lightColor Column { anchors.centerIn: parent spacing: 20 Text { id: resultText color: root.darkColor } Button { anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Start operation") palette.buttonText: root.darkColor onClicked: { appManager.performOperation() } } } Connections { target: appManager function onOperationFinished(result) { resultText.text = "Operation result: " + result } } }
That’s how the application behaves when it started:
Remember that your C++ slots do not need to be void just like the one used in this example. They can actually return a value, but if the execution is going to take a moment, it’s usually better to notify about its result with the signal. Otherwise, the main GUI thread may freeze.
The C++ class can be registered in QML type system in order to use that type as data type in QML code. Also without doing that, Qt Creator won’t display any hints about class properties and members, so your programmer’s live is going to be way more difficult.
Among others, there are two basic ways of registering for C++ classes. You can either register an instantiable C++ class or a non-instantiable C++ class. What’s the difference? Instantiable classes can be created and used just like any other QML type in QML source code.
As a result, you do not need to instantiate an object of class first in C++ and then expose it to QML with context property.
To be able to use your C++ class as QML type, you need to put the QML_ELEMENT or QML_NAMED_ELEMENT macro in class definition. You will get the macro by including <qqml.h>. The macro was introduced in Qt 5.15 and it actually made registering C++ classes more straightforward. That’s how class with this macro should look like:
#ifndef APPMANAGER_H #define APPMANAGER_H #include <QObject> #include <qqml.h> class AppManager : public QObject { Q_OBJECT Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged) QML_ELEMENT public: explicit AppManager(QObject *parent = nullptr); .... }; #endif // APPMANAGER_H
The QML_ELEMENT was originally introduced only for qmake in Qt 5.15 and it was a common problem how to achieve the same with CMake. Thankfully, the solution to use QML_ELEMENT with CMake was introduced in Qt 6.2. CMake new method qt_add_qml_method() is a way to go. It’s used in the default CMakeLists.txt file when you create a new project using Qt Creator’s New Project wizard, but it looks like this:
qt_add_qml_module(testapp URI testapp VERSION 1.0 QML_FILES main.qml )
In order to register C++ classes you need to specify a list of C++ sources to add to a QML module specified with this CMake method. To do that set a SOURCES argument for example in this way and that should be it.
qt_add_qml_module(testapp URI testapp VERSION 1.0 QML_FILES main.qml SOURCES AppManager.h AppManager.cpp )
After importing the module in QML file you should be able to instantiate AppManager class just like any other QML type.
import QtQuick import QtQuick.Controls import testapp // own module Window { id: root readonly property color darkColor: "#218165" readonly property color lightColor: "#EBEBEB" width: 280 height: 150 visible: true title: qsTr("Expose C++ object test") color: appManager.isNightMode ? root.darkColor : root.lightColor Column { anchors.centerIn: parent spacing: 20 Text { color: appManager.isNightMode ? root.lightColor : root.darkColor text: qsTr("Is night mode on? - ") + appManager.isNightMode } Button { anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Change mode") palette.buttonText: appManager.isNightMode ? root.lightColor : root.darkColor // change isNightMode on clicked onClicked: { appManager.isNightMode = !appManager.isNightMode } } } AppManager { id: appManager } }
There are cases when you wouldn’t want C++ classes to be instantiable as QML types. However, you would like to still benefit from recognizing them in QML.
In this post we will explain only one of them which is QML_UNCREATABLE(reason) macro. The macro is intended to be used for example when C++ class have enums or attached properties that you would like to use in QML, but not the class itself.
The macro should be used next to QML_ELEMENT macro in the following way:
#ifndef APPMANAGER_H #define APPMANAGER_H #include <QObject> #include <qqml.h> class AppManager : public QObject { Q_OBJECT Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged) QML_ELEMENT QML_UNCREATABLE("AppManager is for C++ instantiation only") public: explicit AppManager(QObject *parent = nullptr); .... }; #endif // APPMANAGER_H
The reason message will be displayed as an error if you try to instantiate AppManager in QML.
More aspects of registering C++ classes such as registering singletons or enums will be covered in next episodes of „How to integrate QML and C++” series. For now you may consider using the official Qt reference on that topic.
So today you have learned how to properly expose C++ objects and register C++ classes as QML types. It’s crucial to understand how to do that properly. There is no serious Qt project that doesn’t need that.
Therefore, it is good for you to already start using the knowledge you’ve learned in practice. If you want to get more expertise in Qt QML development, read this blog post about how to write clean QML code.
You can unlatch the power of cross-platform development with Qt QML projects. Let Scythe Studio show you how – to enjoy the performance, reach every platform, and benefit from our support. Get in touch today to discover how we can help you!
QML is designed with extensibility in mind, allowing C++ code to be integrated seamlessly. The Qt QML module enables QML objects to be loaded and manipulated from C++. and vice versa. The integration of the QML engine with Qt’s meta-object system allows for direct invocation of C++ functionality from QML.
QML and C++ interact thanks to the fact that Qt uses the meta-object system and everything is based on QObject. That is the source of all of the magic. After you expose C++ object using context property or register C++ classes as QML, there is no limit to extending your QML code.
This blog post is designed for both people who are new to Qt and also for those who know Qt well and just want to read about some news from Qt Quick world. The CMake qt_add_qml_module command is a new thing after all.
If you are new to Qt, then you have to know that it is a good practice and you should keep the logic on the C++ side rather than on the UI code side. This way you keep the source code cleaner.
Let's face it? It is a challenge to get top Qt QML developers on board. Help yourself and start the collaboration with Scythe Studio - real experts in Qt C++ framework.
Discover our capabilitiesThe Qt Graphs is a powerful module of the Qt framework that enables developers to create interactive, both static and […]
Embedded systems are everywhere, from household appliances to industrial machinery. At the heart of these systems is embedded firmware, the […]
Outsourcing embedded systems development has become an increasingly popular strategy for businesses looking to enhance efficiency, reduce costs, and leverage […]