When the decision is done and the technology stack lists C++, Qt and QML it is time to do some work. To make it efficient and clean it is good to know all the available options. So, one of the topics that will come out at the beginning is the communication method between the frontend and the backend.
Q_PROPERTY macro
One of the most popular is the usage of Q_PROPERTY macro. Every class that derives from QObject (and contains Q_OBJECT macro) can use it. The other option is to use Q_GADGET macro. The simplest syntax requires type of the variable, its name and the getter (marked as READ or direct access – MEMBER). The type could be defined by the user or one of the types supported by Qt could be used.
There are a lot of features that Q_PROPERTY offers but aside of the required ones, often used are WRITE and NOTIFY. The first works as a setter (it requires a param of the same type as the property itself) and the second one is a signal that notifies any changes of the property (at least it should, after receiving the signal, the getter is called to check the value).
Most common accessors
The most common accessors are:
- READ – getter, must return either the property’s type or a const reference to that type.
- WRITE – setter, it must return void and must take exactly one argument, either of the property’s type or a pointer or reference to that type.
- MEMBER – could work instead of READ and WRITE, giving direct access to a variable. However, it’s possible to use READ or WRITE in addition to MEMBER.
- RESET – reset the property to the default value, The RESET function must return void and take no parameters.
- NOTIFY – should specify the signal which is emitted when the value (of the property) changes. Qt emits automatically that signal when needed for MEMBER properties that do not have an explicit setter.
- USER (bool) – attribute indicates whether the property is designated as the user-facing or user-editable property for the class. Normally, there is only one USER property per class (default false).
- CONSTANT – means that the property value is constant (exclude WRITE and NOTIFY).
- FINAL – property will not be overridden by a derived class.
- REQUIRED – the property should be set by a user of the class. In QML, classes with REQUIRED properties cannot be instantiated unless all REQUIRED properties have been set.
- BINDABLE (from Qt 6.0) – indicates that the property supports bindings.
Creating a class with Q_Property macro
Below there are two basic examples tow to create a class with Q_PROPERTY macro. The first is based on Qt5 and uses READ WRITE and NOTIFY, the second uses brand new BINDABLE and … does the same thing on QML side but has more to offer for C++.
And to the point, one way to access the variable property from QML is setContextProperty – exposing instance (one) to every component loaded by the engine. Object underneath testObj could be now used in QML by the name myTest. Usage is limited to Q_PROPERTY, signals and public slots (or Q_INVOKABLE functions). But it grants easy access.
Clicking on a button will result in incrementing the variable (calling setter) and through notification signal, the getter is called (READ) and all bindings on QML side are updated.
Maybe a few more words on BINDABLE as it enhances performance and introduces property binding also on C++ side. This help to simplify the program, by eliminating a lot of boilerplate code for tracking and reacting to dependency updates of different objects. Instead of using a setter, it is possible to simply assign a new value as shown below.
Q_INVOKABLE & Public slots
One of these allows calling the function directly from QML. One important disclaimer here. This will transfer ownership to QML side so e.g. if a pointer to an object is returned from the function beware of the garbage collector when C++ also uses this object. This could be tricky to investigate since a garbage collector could not be called right away. To manually change the object ownership QQmlEngine::setObjectOwnership can be used.
QML Connections
QML component that can be used when:
- Multiple connections to the same signal are required.
- Creating connections outside the scope of the signal sender.
- Connecting to targets not defined in QML.
This could be used to intercept any signal that is accessible from defined target. Syntax shown above was introduced in Qt 5.15.
Accessing QML object from C++
It may be needed to change QML property from C++ e.g for testing purposes. To do this there are: QObject::setProperty() or QQmlProperty::write(). Firstly, it will be required to extract a particular QObject pointer like below:
If changing property is not enough it is even possible to call method (using QMetaObject::invokeMethod()) as all QML methods are exposed to the meta-object system.
Summary
The article presented the ways of communication between the frontend and the backend, depending on particular needs. The different methods, starting from variable access, through function calls, acting on emitted signal and ending on QML object access from C++, are shown in the paper.
Sources
- Qt Documentations – Exposing Attributes of C++ Types to QML
- Qt Documentations – QQmlContext Class
- Qt Documentation – Connections QML Type
- Qt Documentation – QQmlContext Class, Set ContextProperty
- Qt Documentation – The Property System
- Qt Documentation – Qt Bindable Properties
- Qt Documentation – Interacting with QML Objects from C++
- Qt Doc Snapshots – QObjectBindableProperty Class
- ICS – Qt 6 Bindings