
Visualisierung von Gesundheitsdaten
Da die Gesundheitsbranche immer mehr Daten generiert, liegt die Herausforderung nicht mehr nur in der Datensammlung, sondern in der Interpretation. […]
Dieses Tutorial über wie man ein Objekt exponiert und eine C++-Klasse in QML registriert ist der erste Beitrag in der neuen Beitragsreihe im Blog von Scythe Studio. Die Serie wird den Titel „Wie man QML und C++ integriert“ tragen. Mit der neuen Qt 6-Version ist es nicht mehr so offensichtlich, wie man diesen entscheidenden Mechanismus in der Qt-Softwareentwicklung richtig und einfach implementiert.
Besonders, da viele Entwickler derzeit von qmake zu CMake wechseln. Deshalb dachten wir, es wäre eine gute Gelegenheit, die verschiedenen Möglichkeiten der Integration von QML und C++ im Detail zu erklären.
Zukünftige Beiträge werden Mechanismen wie Modelle und Plugins behandeln, aber zunächst konzentrieren wir uns auf die Grundlagen und erklären, wie man auf ein C++-Objekt aus QML zugreift und wie man eine C++-Klasse in QML registriert.
Es besteht kein Zweifel, dass QML im Vergleich zu anderen Programmiersprachen, einschließlich C++, hübsch, ansprechend und sogar verlockend ist. Viele gängige Funktionen heutiger Anwendungen könnten ausschließlich mit QML implementiert werden.
Für die Netzwerkommunikation über HTTP können Sie beispielsweise XmlHttpRequest mit JavaScript verwenden, und es gibt QML-Elemente wie ListModel, um Ihre Daten zu speichern.
Das mag vor allem für neue Qt-Quick-Entwickler verlockend sein, bei QML zu bleiben. Doch nach einiger Zeit würden Anwendungen, die ausschließlich in QML geschrieben sind, Wartungsprobleme bekommen.
Kurz gesagt, was sind die Vorteile der Integration von QML und C++?
Sie haben eine klare Trennung zwischen der QML-Benutzeroberfläche und der Anwendungslogik in C++. Das verbessert die Wartbarkeit.
Sie haben Zugriff auf eine Vielzahl von Qt-Modulen und -Funktionen, die nur über die C++-API verfügbar sind.
Sie können plattform-spezifische Funktionen durch Integration von C++ mit Android, Objective-C oder C nutzen.
Sie können einfache C- und C++-Bibliotheken in Qt-Wrappern verwenden, um spezifische Funktionen zu implementieren.
Sie profitieren von einer höheren Anwendungsleistung, insbesondere wenn Sie C++ und Multithreading für intensive Operationen einsetzen. Allerdings muss gesagt werden, dass QML gut optimiert ist.
Wie exponiert man ein C++-Objekt nach QML?
Das Erste, was Sie lernen sollten, ist wie man aus QML auf ein C++-Objekt zugreifen kann. Wann benötigen Sie das? Zum Beispiel, wenn Sie Logik in einer C++-Klasse implementiert haben, aber auf die Eigenschaften, Methoden oder Signale einer Instanz dieser Klasse in QML zugreifen möchten. Das ist ein äußerst gängiger Anwendungsfall in der Qt-Entwicklung.
Lassen Sie uns beginnen, wie man auf eine Eigenschaft einer C++-Klasse in QML zugreifen kann. Zuerst müssen Sie eine neue Klasse erstellen, die die QObject-Klasse erweitert und das Q_OBJECT-Makro verwendet.
Für dieses Tutorial erstellen wir eine Klasse namens AppManager, die als zentraler Punkt für Ereignisse in unserer App gedacht ist.
Die Klasse soll Informationen darüber speichern, ob die App den Dunkelmodus aktiviert hat oder nicht. Zu diesem Zweck fügen wir eine Eigenschaft mithilfe des Makros Q_PROPERTY (type name READ getFunction WRITE setFunction NOTIFY notifySignal) hinzu.
Die Standardverwendung dieses Makros erfordert die Angabe des Typs der Eigenschaft sowie der Namen des Getters, Setters und Signals, die in der Klasse implementiert werden müssen. Daher könnte die Eigenschaftsdeklaration in dieser Klasse wie folgt aussehen:
Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged)
Jetzt müssen Sie nur noch die tatsächlichen Methoden und Signale zur Klassendefinition hinzufügen. Zum Glück müssen Sie das nicht selbst tun. Platzieren Sie einfach den Cursor auf die Eigenschaft und drücken Sie Alt + Enter auf Ihrer Tastatur, um das Refaktorierungsmenü aufzurufen. Dort sollten Sie eine Option finden, um fehlende Eigenschaftsmitglieder automatisch zu generieren.
Tipp: Weitere nützliche Tastenkombinationen finden Sie in unserem Qt Creator Cheat Sheet.
Nach dem Beheben einiger Formatierungsprobleme sollte die AppManager-Klasse folgendermaßen aussehen:
#ifndef APPMANAGER_H #define APPMANAGER_H #include <QObject> class AppManager : public QObject { Q_OBJECT Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged) public: explicit AppManager(QObject *parent = nullptr); bool isNightMode() const; void setIsNightMode(bool isNightMode); signals: void isNightModeChanged(); private: bool m_isNightMode = false; }; #endif // APPMANAGER_H
Dann instanziieren Sie Ihr Klassenobjekt und machen Sie es in der main.cpp-Datei (oder überall dort, wo Sie Zugriff auf die QML-Engine haben) zugänglich, indem Sie die Methode QQmlContext::setContextProperty(const QString &name, QObject value) auf dem Root-Kontext der Engine verwenden. Sie müssen einen Namen angeben, unter dem das Objekt zugänglich sein wird, sowie den Zeiger auf das QML-Objekt. Dieses Paar wird als Kontext-Eigenschaft bezeichnet. Die main.cpp-Datei sollte ungefähr so aussehen:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <AppManager.h> int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // exposing C++ object to Qml AppManager *appManager = new AppManager(&app); engine.rootContext()->setContextProperty("appManager", appManager); const QUrl url(u"qrc:/testapp/main.qml"_qs); 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(); }
Und das wäre alles. Sie müssen lediglich die Methode setContextProperty() ausführen. Ab diesem Punkt ist Ihr C++-Objekt aus QML zugänglich. Als Beispiel werden wir einfach QML-Code ausführen, der das Thema der Anwendung je nach dem Wert der Eigenschaft appManager’s isNightMode ändert. Die Anwendung wird außerdem einen Button enthalten, mit dem Benutzer den Wert der Eigenschaft ändern können.
import QtQuick import QtQuick.Controls Window { id: root readonly property color darkColor: "#218165" readonly property color lightColor: "#EBEBEB" width: 280 height: 150 visible: true title: qsTr("Expose C++ object test") color: appManager.isNightMode ? root.darkColor : root.lightColor Column { anchors.centerIn: parent spacing: 20 Text { color: appManager.isNightMode ? root.lightColor : root.darkColor text: qsTr("Is night mode on? - ") + appManager.isNightMode } Button { anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Change mode") palette.buttonText: appManager.isNightMode ? root.lightColor : root.darkColor // change isNightMode on clicked onClicked: { appManager.isNightMode = !appManager.isNightMode } } } }
Wie Sie sehen können, ist die Eigenschaft des C++-Objekts sowohl zum Lesen als auch zum Schreiben zugänglich. Die Farben und Texte werden automatisch angepasst, dank des Signals isNightModeChanged(), das im Q_PROPERTY-Makro verwendet wird. So verhält sich die Anwendung, wenn sie gestartet wird:
Um eine Methode auf einem C++-Objekt ausführen zu können, müssen Sie das Meta-Objekt-System darüber informieren, dass diese Methode existiert. Dies kann entweder durch Platzieren der Methodendeklaration im public slots-Bereich oder durch Markieren der Methode mit dem Q_INVOKABLE-Makro erfolgen. Das Signal muss im signals-Bereich der Klassendefinition definiert werden.
Mit diesem Wissen nehmen wir an, dass wir eine zeitaufwändige und wahrscheinlich auch ressourcenintensive Datenoperation von QML nach C++ delegieren möchten. Dazu fügen wir die Methode performOperation und das Signal operationFinished(const QString &operationResult) hinzu. Die Methode wird 5 Sekunden warten, um eine zeitaufwändige Operation zu simulieren, und dann ein Signal mit einem zufälligen Ergebnis senden.
Die Klasse sollte dann folgendermaßen aussehen:
#ifndef APPMANAGER_H #define APPMANAGER_H #include <QObject> class AppManager : public QObject { Q_OBJECT Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged) public: explicit AppManager(QObject *parent = nullptr); bool isNightMode() const; void setIsNightMode(bool isNightMode); public slots: void performOperation(); signals: void isNightModeChanged(); void operationFinished(const QString &operationResult); private: bool m_isNightMode = false; }; #endif // APPMANAGER_H
Es ist wichtig, das Meta-Objekt-System über die Methode performOperation zu informieren. Andernfalls erhalten Sie beim Versuch, die Methode aus QML aufzurufen, folgenden Fehler:
TypeError: Property ‚performOperation’ of object AppManager(0x6000027f8ca0) is not a function
So könnte die Methode selbst aussehen:
void AppManager::performOperation() { QTimer *timer = new QTimer(this); timer->setSingleShot(true); connect(timer, &QTimer::timeout, this, [this]() { const int result = QRandomGenerator::global()->generate(); const QString &operationResult = result % 2 == 0 ? "success" : "error"; emit operationFinished(operationResult); }); timer->start(5000); }
An diesem Punkt sollten Sie in der Lage sein, auf die Methode und das Signal der C++-Klasse aus QML zuzugreifen. Lassen Sie uns also mit etwas QML-Coding fortfahren.
import QtQuick import QtQuick.Controls Window { id: root readonly property color darkColor: "#218165" readonly property color lightColor: "#EBEBEB" width: 280 height: 150 visible: true title: qsTr("Expose C++ object test") color: root.lightColor Column { anchors.centerIn: parent spacing: 20 Text { id: resultText color: root.darkColor } Button { anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Start operation") palette.buttonText: root.darkColor onClicked: { appManager.performOperation() } } } Connections { target: appManager function onOperationFinished(result) { resultText.text = "Operation result: " + result } } }
So verhält sich die Anwendung, wenn sie gestartet wird:
Denken Sie daran, dass Ihre C++-Slots, wie der in diesem Beispiel verwendete, nicht unbedingt den Rückgabewert void haben müssen. Sie können tatsächlich einen Wert zurückgeben. Wenn die Ausführung jedoch etwas Zeit in Anspruch nehmen wird, ist es in der Regel besser, das Ergebnis über ein Signal zu melden. Andernfalls könnte der Haupt-GUI-Thread einfrieren.
Eine C++-Klasse kann im QML-Typsystem registriert werden, um diesen Typ als Datentyp im QML-Code zu verwenden. Ohne diese Registrierung zeigt Qt Creator zudem keine Hinweise zu Klassen-Eigenschaften und -Mitgliedern an, was das Programmieren erheblich erschwert.
Es gibt unter anderem zwei grundlegende Möglichkeiten, C++-Klassen zu registrieren: Sie können entweder eine instanziierbare oder eine nicht instanziierbare C++-Klasse registrieren. Was ist der Unterschied? Instanziierbare Klassen können erstellt und genauso wie jeder andere QML-Typ im QML-Quellcode verwendet werden.
Das bedeutet, dass Sie kein Objekt der Klasse zuerst in C++ instanziieren und es anschließend über eine Kontext-Eigenschaft in QML verfügbar machen müssen.
Um Ihre C++-Klasse als QML-Typ verwenden zu können, müssen Sie das Makro QML_ELEMENT oder QML_NAMED_ELEMENT in der Klassendefinition hinzufügen. Sie erhalten das Makro, indem Sie <qqml.h> einbinden. Das Makro wurde in Qt 5.15 eingeführt und hat die Registrierung von C++-Klassen erheblich vereinfacht. So sollte eine Klasse mit diesem Makro aussehen:
#ifndef APPMANAGER_H #define APPMANAGER_H #include <QObject> #include <qqml.h> class AppManager : public QObject { Q_OBJECT Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged) QML_ELEMENT public: explicit AppManager(QObject *parent = nullptr); .... }; #endif // APPMANAGER_H
Das Makro QML_ELEMENT wurde ursprünglich in Qt 5.15 nur für qmake eingeführt, und es war ein häufiges Problem, wie man dasselbe mit CMake erreichen kann. Glücklicherweise wurde in Qt 6.2 eine Lösung eingeführt, um QML_ELEMENT mit CMake zu verwenden. Die neue CMake-Methode qt_add_qml_module() ist der richtige Weg. Sie wird standardmäßig in der CMakeLists.txt-Datei verwendet, wenn Sie ein neues Projekt mit dem „Neues Projekt“-Assistenten von Qt Creator erstellen. So sieht sie aus:
qt_add_qml_module(testapp URI testapp VERSION 1.0 QML_FILES main.qml ) In order to register C++ classes you need to specify a list of C++ sources to add to a QML module specified with this CMake method. To do that set a SOURCES argument for example in this way and that should be it. qt_add_qml_module(testapp URI testapp VERSION 1.0 QML_FILES main.qml SOURCES AppManager.h AppManager.cpp ) After importing the module in QML file you should be able to instantiate AppManager class just like any other QML type. import QtQuick import QtQuick.Controls import testapp // own module Window { id: root readonly property color darkColor: "#218165" readonly property color lightColor: "#EBEBEB" width: 280 height: 150 visible: true title: qsTr("Expose C++ object test") color: appManager.isNightMode ? root.darkColor : root.lightColor Column { anchors.centerIn: parent spacing: 20 Text { color: appManager.isNightMode ? root.lightColor : root.darkColor text: qsTr("Is night mode on? - ") + appManager.isNightMode } Button { anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Change mode") palette.buttonText: appManager.isNightMode ? root.lightColor : root.darkColor // change isNightMode on clicked onClicked: { appManager.isNightMode = !appManager.isNightMode } } } AppManager { id: appManager } }
QML_FILES main.qmlEs gibt Fälle, in denen Sie nicht möchten, dass C++-Klassen als QML-Typen instanziierbar sind. Trotzdem möchten Sie möglicherweise von ihrer Erkennbarkeit in QML profitieren.
QML_FILES main.qmlIn diesem Fall kann das Makro QML_UNCREATABLE(reason) verwendet werden. Dieses Makro ist beispielsweise nützlich, wenn eine C++-Klasse Enums oder angehängte Eigenschaften enthält, die Sie in QML verwenden möchten, aber nicht die Klasse selbst.
QML_FILES main.qmlDas Makro sollte neben dem QML_ELEMENT-Makro in der folgenden Weise verwendet werden:
#ifndef APPMANAGER_H #define APPMANAGER_H #include <QObject> #include <qqml.h> class AppManager : public QObject { Q_OBJECT Q_PROPERTY(bool isNightMode READ isNightMode WRITE setIsNightMode NOTIFY isNightModeChanged) QML_ELEMENT QML_UNCREATABLE("AppManager is for C++ instantiation only") public: explicit AppManager(QObject *parent = nullptr); .... }; #endif // APPMANAGER_H
QML_FILES main.qmlDie Begründungsnachricht (reason message) wird als Fehler angezeigt, wenn Sie versuchen, AppManager in QML zu instanziieren.
QML_FILES main.qmlWeitere Aspekte der Registrierung von C++-Klassen, wie das Registrieren von Singletons oder Enums, werden in den nächsten Episoden der Serie „Wie man QML und C++ integriert“ behandelt. Bis dahin können Sie die offizielle Qt-Dokumentation zu diesem Thema in Betracht ziehen.
QML_FILES main.qmlHeute haben Sie gelernt, wie man C++-Objekte richtig in QML verfügbar macht und wie man C++-Klassen als QML-Typen registriert. Es ist entscheidend zu verstehen, wie dies korrekt gemacht wird, da kein ernstzunehmendes Qt-Projekt ohne diese Techniken auskommt.
QML_FILES main.qmlEs ist daher ratsam, das Gelernte direkt in der Praxis anzuwenden. Wenn Sie Ihre Kenntnisse in der Qt-QML-Entwicklung vertiefen möchten, lesen Sie diesen Blogpost über wie man sauberen QML-Code schreibt.
QML_FILES main.qmlNutzen Sie die volle Leistung der plattformübergreifenden Entwicklung mit Qt-QML-Projekten. Lassen Sie sich von Scythe Studio zeigen, wie Sie von Performance profitieren, jede Plattform erreichen und von unserem Support unterstützt werden können. Kontaktieren Sie uns noch heute, um zu erfahren, wie wir Ihnen helfen können!
QML_FILES main.qmlQML ist so konzipiert, dass es leicht erweiterbar ist und eine nahtlose Integration von C++-Code ermöglicht. Das Qt-QML-Modul erlaubt es, QML-Objekte von C++ aus zu laden und zu manipulieren und umgekehrt. Die Integration der QML-Engine mit dem Meta-Objekt-System von Qt ermöglicht den direkten Aufruf von C++-Funktionalität aus QML.
QML_FILES main.qmlQML und C++ interagieren dank der Tatsache, dass Qt das Meta-Objektsystem verwendet und alles auf QObject basiert. Das ist die Quelle für die gesamte Magie. Sobald Sie ein C++-Objekt mithilfe einer Kontext-Eigenschaft bereitstellen oder C++-Klassen als QML-Typen registrieren, gibt es keine Grenzen für die Erweiterung Ihres QML-Codes.
QML_FILES main.qmlDieser Blogbeitrag richtet sich sowohl an Personen, die neu in Qt sind, als auch an solche, die Qt gut kennen und einfach nur Neuigkeiten aus der Qt Quick-Welt lesen möchten. Der CMake-Befehl qt_add_qml_module ist schließlich eine neue Sache.
QML_FILES main.qmlWenn Sie neu in Qt sind, sollten Sie wissen, dass es eine gute Praxis ist, die Logik auf der C++-Seite zu belassen und nicht auf der UI-Code-Seite. Auf diese Weise bleibt der Quellcode sauberer.
Kommen wir zur Sache: Es ist eine Herausforderung, Top-Qt-QML-Entwickler zu finden. Helfen Sie sich selbst und starten Sie die Zusammenarbeit mit Scythe Studio – echten Experten im Qt C++ Framework.
Entdecken Sie unsere Fähigkeiten!Da die Gesundheitsbranche immer mehr Daten generiert, liegt die Herausforderung nicht mehr nur in der Datensammlung, sondern in der Interpretation. […]
Produkte aus der STM32-Familie sind seit langer Zeit ein beliebtes Ziel für eingebettete Qt-Anwendungen. Eine der beliebtesten Optionen war über […]
Ich begrüße Sie zu einem weiteren Blogbeitrag. Der letzte Beitrag behandelte eine Form der Kommunikation zwischen Geräten mit Qt Bluetooth […]