New Qt 6 Bindings Deliver Increased Performance and Code Reliability
The latest release of Qt, version 6.0, arrived not only with API cleanups but also improvements in performance. The binding engine was brought into the C++ side of the Qt API, allowing C++ developers to take advantage of the powers of binding. This not only enhances performance, but also code readability. In this blog post, I’ll explain how to best use the newly added binding functionality in Qt 6.0 and provide a full, simple example to test run on your own machine.
C++ to QML Binding
Before we even look at bindings in C++, we need to look at what it is or how it operates. The best way is to look at it in QML, where it originates.
Rectangle {
id: exampleRect
color: "blue"
height: 200
width: height + 20
anchors.centerIn: parent
}
In the above example, there is a binding; the binding exists for the width property of the rectangle. When height is changed, the width property will also update to reflect that there's a new value for height. This is done primarily with signals behind the scene. When the width is notified that a property it is bound to has changed, it will re-evaluate the expression to the right.
In C++, before Qt 6, we can add custom properties that are exposed to the QML side by using the Q_PROPERTY macro:
Q_PROPERTY(int count READ getCount WRITE setCount NOTIFY countChanged)
These properties were not bindable, at least on the C++ side. If there was a change, we would simply emit the change signal of the respective property each time we changed it inside the setter method. Using only the assignment operator would not trigger a change signal.
Starting in Qt 6.0, we can specify a C++ property as bindable by doing the following:
Q_PROPERTY(int count READ getCount WRITE setCount NOTIFY countChanged BINDABLE bindableCount)
We have our usual getter, setter, and notify pieces of the Q_PROPERTY with the added BINDABLE, which takes a special method similar to the following:
QBindable<int> bindableCount() { return &b_count; }
It is good to note that the NOTIFY piece is optional, but the change signal is necessary to know when the property has changed.
But this is not yet complete, you will also need to add a special private member to your class using the Q_OBJECT_BINDABLE_PROPERTY macro:
Q_OBJECT_BINDABLE_PROPERTY(SomeClass, int, b_count, &SomeClass::countChanged)
Which is basically:
Q_OBJECT_BINDABLE_PROPERTY(ClassName, Type, VarName, &ClassName::ChangeSignal)
In our C++ code, when we do:
b_count = 10;
The notification that the property, count, has changed will automatically be emitted behind the scene since the property is marked bindable. This reduces the need of calling an intermediate method such as the setter and also simplifies the code.
Code Example
Let's look at a simple example. It is a very basic application that has a class named RectangleManager. This class has an integer property, rectangleCount, that is exposed to QML. In QML, the RectangleManager's property will be displayed to the screen with a Text object.
Normally, a class as such will look like this:
RectangleManager.h - Before Qt 6
class RectangleManager : public QObject
{
Q_OBJECT
Q_PROPERTY(int rectangleCount READ rectangleCount,
WRITE setRectangleCount,
NOTIFY rectangleCountChanged)
public:
explicit RectangleManager(QObject *parent = nullptr);
int rectangleCount() const;
public slots:
void setRectangleCount(int rectangleCount);
signals:
void rectangleCountChanged();
private:
int m_rectangleCount;
};
We have a class that is derived from a QObject. We added a Q_PROPERTY for the rectangleCount property, which is exposed to QML. QML will automatically know to call the respective getter and setter methods as needed.
For Qt 6, we can update our Q_PROPERTY and make it bindable with adding the necessary methods and private members:
class RectangleManager : public QObject
{
Q_OBJECT
Q_PROPERTY(int rectangleCount READ rectangleCount,
WRITE setRectangleCount,
BINDABLE bindableRectangleCount
)
public:
explicit RectangleManager(QObject *parent = nullptr);
int rectangleCount() const;
QBindable<int> bindableRectangleCount() { return &b_rectangleCount; }
public slots:
void setRectangleCount(int rectangleCount);
signals:
void rectangleCountChanged();
private:
Q_OBJECT_BINDABLE_PROPERTY(RectangleManager,
int,
b_rectangleCount,
&RectangleManager::rectangleCountChanged)
};
In our implementation files:
RectangleManager.cpp - Before Qt 6
...
void RectangleManager::setRectangleCount(int rectangleCount)
{
if (m_rectangleCount == rectangleCount)
return;
m_rectangleCount = rectangleCount;
emit rectangleCountChanged();
}
...
Before Qt 6, we only update our internal variable if the value is indeed changing and emit the signal.
In Qt 6.0 and beyond, this same function is simplified to only include one line:
RectangleManager.cpp - After Qt 6
...
void RectangleManager::setRectangleCount(int rectangleCount)
{
b_rectangleCount = rectangleCount;
}
...
This is because behind the scenes, the comparison for the current value and the new value is done in the API code.
The QML code is simple:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Text {
id: name
text: rectangleManager.rectangleCount
font.pixelSize: 30
anchors.centerIn: parent
}
}
Of course, if you run this you will not see the binding at action. So let's add a timer which will increment the property by assignment. Add the following code to your RectangleManager's constructor:
RectangleManager.cpp - After Qt 6
...
QTimer *timer = new QTimer(this);
timer->setInterval(1000);
timer->start();
connect(timer, &QTimer::timeout, this, [&]() {
static int i = 0;
b_rectangleCount = 10 + i;
i++;
});
...
If you run the application now, the text displaying the rectangleCount will be updated to reflect the b_rectangleCount = 10 + i; assignment at every 1000 milliseconds update.
Conclusion
Changing existing Q_PROPERTY's to be bindable is fairly easy and the reward is better performance, readability, and maintainability of the code. Looking for more great Qt/QML content? Start here.