Scythe Studio website employs cookies to improve your user experience (Read more)

How to integrate Qml and C++? Expose object and register C++ class to Qml

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.

Why to integrate Qml and C++?

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 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++?

  • You have a clean division between your application’s UI code in Qml and application’s logic in C++. This means improved maintainability.
  • You have access to a wide range of Qt modules and features that are accessible only from C++ API.
  • You can access platform-specific features by integrating C++ with Android, Objective-C or C.
  • You can use plain C and C++ libraries in Qt wrappers to implement specific features.
  • You have application of higher performance. Especially when you use C++ and multi-threading for intensive operations. However, it must be said that Qml is well optimized.

How to expose C++ object to Qml?

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.

Access property of C++ class in Qml

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 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 engine) using QQmlContext::setContextProperty(const QString &name, QObject *value) method on engine’s root context. You must pass a name under which object will be accessible and the pointer to an object. 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 write simple 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:

 

night mode

How to execute a method and handle a signal of C++ object?

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 like when it started:

 

c++ object test

Remember that your C++ slots do not need to be void just like 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 signal. Otherwise, the main GUI thread may freeze.

How to register C++ class to Qml?

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

How to register instantiable C++ class to Qml?

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 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
  }
}

How to register non-instantiable C++ class to Qml?

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.

Summary

So today you have learned how to properly expose C++ object and register C++ class as Qml type. 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.

In the next episode of the „How to integrate Qml and C++?” series, we will cover registering C++ class as singleton. Stay tuned and await future blog posts on this topic!

Scythe-Studio - Chief Executive Officer

Łukasz Kosiński Chief Executive Officer

Latest posts

Scythe Studio becomes a Qt Service Partner main image

Scythe Studio becomes a Qt Service Partner

Qt Service Partner is a title that Scythe Studio company can be proud of. As of 1st November 2021, we strengthen ties with the Qt framework ecosystem which is natural for us as Qt software development experts. From the very beginning, our goal was to provide superb quality software consulting services in the technology of our first choice – Qt. Therefore, for the Scythe Studio company being a Qt Service Partner is a real ennoblement and appreciation of the good spirit that we spread in a community.

Scythe-Studio - Chief Executive Officer

Łukasz Kosiński Chief Executive Officer