Raymii.org
Quis custodiet ipsos custodes?Home | About | All pages | Cluster Status | RSS Feed
QT / QML Signals and Slots with C++
Published: 25-02-2021 | Author: Remy van Elst | Text only version of this article
❗ This post is over three years old. It may no longer be up to date. Opinions may have changed.
Table of Contents
QT has an Observer mechanism built in, which they call 'Signals and Slots'. It allows objects to communicate with each other without having to have knowledge of either ones internals. By inheriting from QObject
and defining a few Q_PROPERTY
macro's, the QT Meta Object Compiler (moc
) does all the hard work for you.
Inside a C++ class this all works handy dandy and is reasonable easy to follow, but when using QML it requires a bit more work. This small example shows you how to bind QML and C++ together using signals and slots, in QT 5.12.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!
Below a screenshot of the application. It's nothing more than a simple counter that increments with a button or is set via a text input field, but it is enough to get you started.
Because this signal/slot construction is mostly string based, you cannot use the refactoring
tools provided by your IDE. If your method is based on value
and you want to change
value
to, lets say, something
, you need to change the Q_PROPERTY
, the QML usage
and bindings and all the regular C++ code. Not very obvious if you're not familiar with
how QT signals and slots and QML work.
For fun I compiled this example application to Webassembly as well. You can
run it here, or at the bottom of this page it's embedded as an iframe
.
Summary
Because this is a small snippet, it lacks the explanation and depth you normally get from my articles. Some code comments are provided, but the QT documentation is recommended reading in this case:
It's explained there very extensive. That is also why I wrote up this summary, due to all the comprehensive documentation, it's hard to get started with something small.
My example code has a C++ class named Counter
, with one private long long
named m_Value
.
In the QML file I want to use this class and its methods, including the QT Signal/Slot.
The class must inherit from QObject
and you must place the Q_OBJECT
macro in the header:
class Counter : public QObject
{
Q_OBJECT
[...]
The methods for setting and getting the value are as you'd expect:
long long value() const { return m_Value; };
[...]
void Counter::setValue(long long value) {
if (value == m_Value)
return;
m_Value = value;
emit valueChanged(value);
}
In the above method you see the emit
keyword. That's a blank define, for clarity. The
function valueChanged()
is called. This is our signal
, as in the header file:
signals:
void valueChanged(long long newValue);
The setValue()
method is our slot
:
public slots:
void setValue(long long value);
These are accessible to QML by this Q_PROPERTY
line:
Q_PROPERTY(long long value READ value WRITE setValue NOTIFY valueChanged)
You could also connect these up to things via QObject::connect()
but that is
out of this snippets scope. That is for when you use the signaling inside C++.
These lines in main.cpp
is also required, it adds your class to QML so to say:
QQmlApplicationEngine engine;
Counter myCounter;
QQmlContext *context = engine.rootContext();
context->setContextProperty("MyCounter", &myCounter);
After this you can access MyCounter
inside of QML as if it was a
regular C++ class. FOr example, to call the Counter::value()
method:
Text {
text: "Counter: " + MyCounter.value + "."
}
Or the Counter::setValue()
method:
Button {
text: qsTr("Set counter to 10")
// C++ method Counter::setValue(long long), bound via Q_PROPERTY
onClicked: MyCounter.setValue(10)
}
Due to the magic of the moc
and the extra code it generates via Q_PROPERTY
,
when you increment like in the below example, it knows which value to increment
and has generated correct operator overloads for it:
Button {
text: qsTr("Increase Counter")
onClicked: ++MyCounter.value
}
You can also receive the C++ signal right in QT. We've defined valueChanged
as the
signal and via a Connection
with onValueChanged
(capitals matter here, prefix your
method with on
and change the first character of your method name to a capital) you
can do things in QML. Like below, where I have a local variable that is incremented
each time the signal is received:
Text {
property int changeCount: 0
id: labelChanged
text: "Count has changed " + changeCount + " times."
// Receive the valueChanged NOTIFY
Connections {
target: MyCounter
onValueChanged: {
++labelChanged.changeCount
}
}
}
For a bi-directional binding example, look at the last TextInput
in QML. It shows the current
value of the C++ class, updates when the value is updated and when you enter a number,
it updates the C++ class.
Example code
Create a project folder and place all the files there under the filenames provided.
The project is also available on github, here.
qmlcppsignalexample.pro
QT += quick
CONFIG += c++11
SOURCES += \
counter.cpp \
main.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
counter.h
qml.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include <QQmlContext>
#include "counter.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Counter myCounter;
QQmlContext *context = engine.rootContext();
/* Below line makes myCounter object and methods available in QML as "MyCounter" */
context->setContextProperty("MyCounter", &myCounter);
const QUrl url(QStringLiteral("qrc:/main.qml"));
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();
}
counter.h
#ifndef COUNTER_H
#define COUNTER_H
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
Q_PROPERTY(long long value READ value WRITE setValue NOTIFY valueChanged)
public:
explicit Counter(QObject *parent = nullptr);
long long value() const { return m_Value; };
public slots:
void setValue(long long value);
signals:
void valueChanged(long long newValue);
private:
long long m_Value {0} ;
};
#endif // COUNTER_H
counter.cpp
#include "counter.h"
Counter::Counter(QObject* parent) : QObject(parent)
{
}
void Counter::setValue(long long value) {
if (value == m_Value)
return;
m_Value = value;
emit valueChanged(value);
}
main.qml
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.11
Window {
width: 640
height: 480
visible: true
title: qsTr("QML Signals and slots example - Raymii.org")
MenuBar {
width: parent.width
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 20
Text {
id: info
width: parent.width * 0.9
wrapMode: Text.WordWrap
text: "QML / C++ binding via signals and slots example program, by Raymii.org. License: GNU GPLv3"
}
Text {
id: labelCount
// C++ method Counter::value(). Bound via Q_PROPERTY, updates automatically on change
text: "Counter: " + MyCounter.value + "."
}
Text {
property int changeCount: 0
id: labelChanged
text: "Count has changed " + changeCount + " times."
// Receive the valueChanged NOTIFY
Connections {
target: MyCounter
onValueChanged: {
++labelChanged.changeCount
}
}
}
Row {
spacing: 20
Button {
text: qsTr("Increase Counter")
onClicked: ++MyCounter.value
}
Button {
text: qsTr("Set counter to 10")
// C++ method Counter::setValue(long long), bound via Q_PROPERTY
onClicked: MyCounter.setValue(10)
}
Button {
text: qsTr("Reset")
onClicked: {
// C++ method Counter::setValue(long long), bound via Q_PROPERTY
MyCounter.setValue(0)
}
}
}
Row {
spacing: 20
Text {
id: setText
text: qsTr("Enter counter value: ")
}
Rectangle {
width: setText.width
height: setText.height
border.width: 1
border.color: "black"
TextInput {
id: counterInput
focus: true
text: MyCounter.value
}
}
// Bi-directional binding, entering a number in the textarea updates the
// C++ class, if the C++ class is updated, the textarea is updated as well.
Binding {
target: MyCounter
property: "value"
value: counterInput.text
}
}
}
}
Build / Make
To create the above code, first create a build folder outside of the project:
cd /tmp
mkdir build-qmlexample
cd build-qmlexample
Run qmake
, replace the path (/home/remy/tmp/qt/qml_cpp_signal_example/
) to your project path:
qmake /home/remy/tmp/qt/qml_cpp_signal_example/qmlcppsignalexample.pro -spec linux-g++ CONFIG+=release && make qmake_all
This example uses qmake
, but there should be no trouble using cmake
. Not using anything fancy here.
When qmake
has finished, you can run make
to build the project:
make -j4
After a short while, the binary should be available:
$ file qml_cpp_signal_example
qml_cpp_signal_example: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f884f57b90ebf05b51551d42cef5ca3ee52037b4, for GNU/Linux 3.2.0, with debug_info, not stripped
Run it from the commandline:
./qml_cpp_signal_example
QT Webassembly Demo
For fun I compiled the example application to webassembly. Run it here
or, if it loads, an iframe
below: