QT hat einen eingebauten Observer-Mechanismus, den sie „Signals and Slots“ nennen. Es ermöglicht Objekten, miteinander zu kommunizieren, ohne die Interna eines der beiden kennen zu müssen. Durch Erben von QObject
und einige wenige Q_PROPERTY
definieren Makros, der QT Meta Object Compiler (moc
) erledigt die ganze harte Arbeit für Sie. Innerhalb einer C++-Klasse funktioniert das alles praktisch und ist relativ einfach zu befolgen, aber wenn Sie QML verwenden, erfordert es etwas mehr Arbeit. Dieses kleine Beispiel zeigt Ihnen, wie Sie QML und C++ mithilfe von Signalen und Slots in QT 5.12 verbinden.
Unten ein Screenshot der Anwendung. Es ist nichts weiter als ein einfacher Zähler, der per Knopfdruck hochgezählt oder über ein Texteingabefeld gesetzt wird, aber für den Anfang reicht es aus.
Da diese Signal-/Slot-Konstruktion hauptsächlich auf Zeichenfolgen basiert, können Sie die von Ihrer IDE bereitgestellten Refactoring-Tools nicht verwenden. Wenn Ihre Methode auf value
basiert und Sie value
ändern möchten zu, sagen wir, something
, müssen Sie den Q_PROPERTY
ändern , die QML-Nutzung und -Bindungen und den gesamten regulären C++-Code. Nicht sehr offensichtlich, wenn Sie nicht damit vertraut sind, wie QT-Signale und -Slots und QML funktionieren.
Zum Spaß habe ich diese Beispielanwendung auch für Webassembly kompiliert. Sie können es hier ausführen oder es ist unten auf dieser Seite als iframe
eingebettet .
Zusammenfassung
Da dies ein kleiner Ausschnitt ist, fehlt ihm die Erklärung und Tiefe, die Sie normalerweise von meinen Artikeln erhalten. Einige Codekommentare werden bereitgestellt, aber in diesem Fall wird empfohlen, die QT-Dokumentation zu lesen:
- Signale und Slots
- Interaktion mit QML und C++
Da ist es sehr ausführlich erklärt. Das ist auch der Grund, warum ich diese Zusammenfassung geschrieben habe, bei all der umfangreichen Dokumentation ist es schwer, mit etwas Kleinem anzufangen.
Mein Beispielcode hat eine C++-Klasse namens Counter
, mit einem privaten long long
mit dem Namen m_Value
.In der QML-Datei möchte ich diese Klasse und ihre Methoden verwenden, einschließlich des QT-Signals/Slots.
Die Klasse muss von QObject
erben und Sie müssen den Q_OBJECT
platzieren Makro im Header:
class Counter : public QObject
{
Q_OBJECT
[...]
Die Methoden zum Festlegen und Abrufen des Werts sind wie erwartet:
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 der obigen Methode sehen Sie den emit
Stichwort. Das ist aus Gründen der Übersichtlichkeit eine leere Definition. Die Funktion valueChanged()
wird genannt. Das ist unser signal
, wie in der Header-Datei:
signals:
void valueChanged(long long newValue);
Die setValue()
Methode ist unser slot
:
public slots:
void setValue(long long value);
Diese sind für QML über diesen Q_PROPERTY
zugänglich Zeile:
Q_PROPERTY(long long value READ value WRITE setValue NOTIFY valueChanged)
Sie können diese auch über QObject::connect()
mit Dingen verbinden aber das ist außerhalb des Bereichs dieses Snippets. Das gilt, wenn Sie die Signalisierung in C++ verwenden.
Diese Zeilen in main.cpp
ist ebenfalls erforderlich, es fügt sozusagen Ihre Klasse zu QML hinzu:
QQmlApplicationEngine engine;
Counter myCounter;
QQmlContext *context = engine.rootContext();
context->setContextProperty("MyCounter", &myCounter);
Danach können Sie auf MyCounter
zugreifen innerhalb von QML, als wäre es eine reguläre C++-Klasse. Zum Beispiel, um Counter::value()
anzurufen Methode:
Text {
text: "Counter: " + MyCounter.value + "."
}
Oder die Counter::setValue()
Methode:
Button {
text: qsTr("Set counter to 10")
// C++ method Counter::setValue(long long), bound via Q_PROPERTY
onClicked: MyCounter.setValue(10)
}
Aufgrund der Magie der moc
und den zusätzlichen Code, den es über Q_PROPERTY
generiert , wenn Sie wie im folgenden Beispiel inkrementieren, weiß es, welcher Wert zu inkrementieren ist, und hat dafür korrekte Operatorüberladungen generiert:
Button {
text: qsTr("Increase Counter")
onClicked: ++MyCounter.value
}
Sie können das C++-Signal auch direkt in QT empfangen. Wir haben valueChanged
definiert als das Signal und über einen Connection
mit onValueChanged
(Hier kommt es auf Großbuchstaben an, stellen Sie Ihrer Methode on
voran und ändern Sie das erste Zeichen Ihres Methodennamens in einen Großbuchstaben), können Sie Dinge in QML tun. Wie unten, wo ich eine lokale Variable habe, die jedes Mal erhöht wird, wenn das Signal empfangen wird:
Text {
property int changeCount: 0
id: labelChanged
text: "Count has changed " + changeCount + " times."
// Receive the valueChanged NOTIFY
Connections {
target: MyCounter
onValueChanged: {
++labelChanged.changeCount
}
}
}
Ein Beispiel für eine bidirektionale Bindung finden Sie im letzten TextInput
in QML. Es zeigt den aktuellen Wert der C++-Klasse an, wird aktualisiert, wenn der Wert aktualisiert wird, und wenn Sie eine Zahl eingeben, aktualisiert es die C++-Klasse.
Beispielcode
Erstellen Sie einen Projektordner und legen Sie dort alle Dateien unter den angegebenen Dateinamen ab.
Das Projekt ist auch hier auf Github verfügbar.
qmlcppsignalebeispiel.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
}
}
}
}
Bauen / Machen
Um den obigen Code zu erstellen, erstellen Sie zuerst einen Build-Ordner außerhalb des Projekts:
cd /tmp
mkdir build-qmlexample
cd build-qmlexample
Führen Sie qmake
aus , ersetzen Sie den Pfad (/home/remy/tmp/qt/qml_cpp_signal_example/
) zu Ihrem Projektpfad:
qmake /home/remy/tmp/qt/qml_cpp_signal_example/qmlcppsignalexample.pro -spec linux-g++ CONFIG+=release && make qmake_all
Dieses Beispiel verwendet qmake
, aber es sollte keine Probleme geben, cmake
zu verwenden . Hier wird nichts Besonderes verwendet.
Wenn qmake
beendet ist, können Sie make
ausführen um das Projekt zu erstellen:
make -j4
Nach kurzer Zeit sollte die Binärdatei verfügbar sein:
$ 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
Führen Sie es von der Befehlszeile aus:
./qml_cpp_signal_example
QT-Webassembly-Demo
Zum Spaß habe ich die Beispielanwendung zu Webassembly kompiliert. Führen Sie es hier aus oder, wenn es geladen wird, ein iframe
unten: