Dieser Artikel ist die vollständigste Beschreibung von Signalen und Slots in QML im Vergleich zu allen vorherigen Artikeln auf dieser Seite.
In diesem Artikel werde ich versuchen, Folgendes zu erklären, wenn ich mit Qt/QML + Qt/C++ arbeite:
- Möglichkeiten, Signale und Slots zu deklarieren, die in einer C++-Klasse auch Methoden genannt werden, um in der QML-Schicht registriert zu werden
- Verbindungswege zu den Signalen von Klassen, die in C++ als Kontext deklariert sind
- Arbeite mit Q_PROPERTY, was ebenfalls Signale und Slots benötigt
- Möglichkeiten, Signale und Slots in QML zu verbinden
- usw.
Signale und Slots aus einer C++-Klasse
Lassen Sie uns unsere erste Klasse erstellen, die mit Signalen und Slots in QML funktioniert. Dies ist eines der allerersten Beispiele, die ich bereits gezeigt habe, aber ich werde dieses Beispiel wiederholen, um den Artikel so vollständig wie möglich zu machen.
In diesem Beispiel möchte ich eine Anwendung mit einer Schaltfläche erstellen, und das Drücken dieser Schaltfläche erhöht einen Zähler, der sich in einer C++-Klasse befindet. Diese C++-Klasse wird als Kontexteigenschaft in der QML-Engine unserer Anwendung registriert.
Das Aussehen der Anwendung wird wie folgt sein
AppCore.h
Das Deklarieren von Signalen und Slots in C++-Code unterscheidet sich nicht wesentlich von klassischem Qt/C++.
#ifndef APPCORE_H #define APPCORE_H #include <QObject> class AppCore : public QObject { Q_OBJECT public: explicit AppCore(QObject *parent = nullptr); signals: void sendToQml(int count); public slots: void receiveFromQml(); private: int m_counter {0}; }; #endif // APPCORE_H
AppCore.cpp
Sowie die Umsetzung der Methoden selbst.
#include "AppCore.h" AppCore::AppCore(QObject* parent) : QObject(parent) { } void AppCore::receiveFromQml() { // We increase the counter and send a signal with its value ++m_counter; emit sendToQml(m_counter); }
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "AppCore.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); AppCore appCore; // Create the application core with signals and slots QQmlApplicationEngine engine; QQmlContext *context = engine.rootContext(); /* We load the object into the context to establish the connection, * and also define the name "appCore" by which the connection will occur * */ context->setContextProperty("appCore", &appCore); 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(); }
main.qml
Und jetzt das Interessanteste. Wie man ein in einem QML-Kontext geladenes Objekt verwendet und wie man sich mit seinen Signalen verbindet.
Wie Sie sich erinnern, haben wir das Objekt unter dem Namen appCore in den QML -Kontext geladen, wir werden dieses Objekt verwenden, um darauf zuzugreifen. Aber um eine Verbindung zu einem Signal herzustellen, müssen wir den QML-Typ Connections verwenden.
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 Window { visible: true width: 640 height: 480 title: qsTr("QML Signals and Slots") /* Using the Connections Object * Establish a connection with the application core object * */ Connections { target: appCore // Specify the target to connect /* Declare and implement the function as a parameter * object and with a name similar to the name of the signal * The difference is that we add on at the beginning and then write * capitalized * */ onSendToQml: { labelCount.text = count // Set the counter to the text label } } Label { id: labelCount text: "0" anchors.bottom: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 15 } Button { text: qsTr("Increase counter") onClicked: appCore.receiveFromQml() // Вызов слота anchors.top: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter } }
Auf diese Weise können Sie auf ein Objekt zugreifen, das in den QML-Engine-Kontext geladen wurde, seinen Slot aufrufen und das Signal von diesem Objekt verarbeiten.
In diesem Fall muss auch receiveFromQml() nicht als Slot deklariert werden. Diese Methode kann auch als Q_INVOKABLE -Methode deklariert werden.
public: explicit AppCore(QObject *parent = nullptr); Q_INVOKABLE void receiveFromQml();
Verwenden von Q_PROPERTY
Die nächste Option ist die Verwendung des Makros Q_PROPERTY. Eine klassische Eigenschaft in Qt für unsere Aufgabe könnte so aussehen
Q_PROPERTY(int counter READ counter WRITE setCounter NOTIFY counterChanged)
Diese Eigenschaft hat die folgenden Komponenten:
- Eigenschaftstyp sowie sein Name: int Zähler , die an die Variable int m_counter innerhalb der Klasse gebunden sind, dies ist die Codegenerierungslogik in Qt
- Methodenname lesen, gleich Eigenschaftsname: counter
- Methodenname zum Setzen des Wertes: setCounter
- Signal, das Eigenschaftsänderungen anzeigt: counterChanged
Sie können diesem Makro auch zusätzliche Parameter übergeben, aber das würde den Rahmen dieses Artikels sprengen. Und auch die Eigenschaft kann schreibgeschützt sein, also ohne Setter.
Sehen Sie sich nun den vollständigen Code mit Q_PROPERTY an.
AppCore.h
#ifndef APPCORE_H #define APPCORE_H #include <QObject> class AppCore : public QObject { Q_OBJECT public: Q_PROPERTY(int counter READ counter WRITE setCounter NOTIFY counterChanged) explicit AppCore(QObject *parent = nullptr); int counter() const; public slots: void setCounter(int counter); signals: void counterChanged(int counter); private: int m_counter {0}; }; #endif // APPCORE_H
AppCore.cpp
#include "AppCore.h" AppCore::AppCore(QObject* parent) : QObject(parent) { } int AppCore::counter() const { return m_counter; } void AppCore::setCounter(int counter) { if (m_counter == counter) return; m_counter = counter; emit counterChanged(m_counter); }
main.qml
Hier sehen Sie, dass das Verbinden und Zugreifen auf die Eigenschaft durch den deklarativen Stil des QML-Codes erleichtert wird. Natürlich können Sie nicht immer Eigenschaften verwenden, manchmal müssen Sie nur Signale, Slots und Q_INVOKABLE-Methoden verwenden. Aber für Variablen wie Zähler sind Eigenschaften wahrscheinlich viel bequemer.
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 Window { visible: true width: 640 height: 480 title: qsTr("QML Signals and Slots") Label { id: labelCount text: appCore.counter anchors.bottom: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 15 } Button { text: qsTr("Increase counter") onClicked: ++appCore.counter anchors.top: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter } }
Verbinden von Signalen innerhalb von QML-Dateien
Betrachten Sie nun die Möglichkeit, Signale und Slots (Funktionen) innerhalb von QML-Dateien zu verbinden. Es wird keinen C++-Code mehr geben.
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 Window { id: mainWindow visible: true width: 640 height: 480 title: qsTr("QML Signals and Slots") // Counter property property int counter: 0 // Method for counter manipulation function inrementCounter() { ++counter; } Label { id: labelCount text: mainWindow.counter anchors.bottom: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 15 } Button { id: button text: qsTr("Increase counter") anchors.top: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter Component.onCompleted: { // When the button is created, then connect the click signal on the button // to the method for increasing the counter in the application window button.clicked.connect(mainWindow.inrementCounter) } } }
Unter anderem können Sie Signale von Slots verwenden und deaktivieren.
button.clicked.disconnect(mainWindow.inrementCounter)
Signal mit Signal verbinden
Auch in QML ist es noch möglich, ein Signal mit einem Signal zu verbinden, wie in Qt/C++. Betrachten Sie das folgende künstliche Beispiel.
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 Window { id: mainWindow visible: true width: 640 height: 480 title: qsTr("QML Signals and Slots") // Announcing a button click signal in the application window signal buttonClicked; Label { id: labelCount text: counter anchors.bottom: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 15 // Counter property property int counter: 0 // Method for counter manipulation function inrementCounter() { ++counter; } } Button { id: button text: qsTr("Increase counter") anchors.top: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter Component.onCompleted: { // When the button is created, then connect the click signal on the button // to the method for increasing the counter in the application window button.clicked.connect(mainWindow.buttonClicked) } } Component.onCompleted: { buttonClicked.connect(labelCount.inrementCounter) } }
In diesem Fall erhöht sich der Zähler weiter, wenn die Taste gedrückt wird. Das Tastendrucksignal ist jedoch nicht direkt mit der Zählerinkrementfunktion verbunden, sondern wird über ein Signal übertragen.
Verwendung von Variablen in Signalen
QML hat auch die Fähigkeit, Variablen in Signalen zu verwenden.
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 Window { id: mainWindow visible: true width: 640 height: 480 title: qsTr("QML Signals and Slots") // Signal with argument signal setCounter(var number); Label { id: labelCount text: counter anchors.bottom: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 15 // Counter property property int counter: 0 // Method for counter manipulation, takes an argument function setCounter(number) { counter = number; } } Button { id: button text: qsTr("Increase counter") anchors.top: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter onClicked: { // We call the signal of the application window for installing the counter indicating the new counter value mainWindow.setCounter(labelCount.counter + 1) } } Component.onCompleted: { setCounter.connect(labelCount.setCounter) } }
Fazit
Zum größten Teil passt dieser gesamte Artikel in ein paar Punkte:
- In C++ können Sie Signale, Slots und Q_INVOKABLE-Methoden verwenden und Eigenschaften mit dem Q_PROPERTY-Makro erstellen, um mit der QML-Schicht zu interagieren
- Um auf Signale von Objekten zu reagieren, können Sie den Typ QML-Verbindungen verwenden
- Q_PROPERTY folgt dem deklarativen QML-Stil und kann automatisch neue Werte setzen, wenn eine Eigenschaft geändert wird, wenn die Eigenschaft zu einem beliebigen Objekt in QML hinzugefügt wurde. In diesem Fall werden Signal-Slot-Verbindungen automatisch hergestellt.
- In QML können Sie Signal-/Slot-Verbindungen mit der folgenden Syntax aktivieren und deaktivieren:
- object1.signal.connect (object2.slot)
- object1.signal.disconnect (object2.slot)
- Signale in QML können auch mit anderen Signalen verbunden werden, wie es in Qt/C++ gemacht wird
- Signale in QML können auch Argumente haben