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

Qt Qml development
2022-04-20
12 minutes
Expose object and register C++ class to QML

Introduction

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

  • You have a clean division between your application’s QML UI native code and the 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 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:

 

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

 

 

c++ object test

 

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.

 

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

 

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

 

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

 

FAQs

 

How can C++ be used to extend QML?

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.

 

How do C++ and QML interact?

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.

 

Is it a common case to register C++ classes as QML types?

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.

Scythe-Studio - Chief Executive Officer

Łukasz KosiΕ„ski Chief Executive Officer

NeedΒ QtΒ QML developmentΒ services?

service partner

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 capabilities

Latest posts

[ 134 ]