- 1. Container.h
- 2. Container.cpp
- 3. Objektfabrik
- 1. Fabrik.h
- 2. Factory.cpp
- 4. main.cpp
- 5. main.qml
Einer der unbestrittenen Vorteile von QML in Qt besteht darin, dass Sie die Backend-Logik ziemlich scharf von der Anwendungsschnittstelle trennen können. Das heißt, wir schreiben das gesamte Backend in C++ und in QML zeigen wir nur das notwendige Ergebnis an.
Gleichzeitig können wir auch die interne Logik rein in QML schreiben, wodurch das Vorhandensein von C++-Code minimiert wird, oder umgekehrt das Design einiger Elemente in C++ vorschreiben, indem wir die Fähigkeiten von OpenGL verwenden. Trotz der Trennung der QML- und C++-Teile des Codes in Backend - und Frontend -Anwendungen haben wir keine großen Einschränkungen.
Aber manchmal ist es notwendig, eine Datenstruktur, die mehrere Felder mit Informationen enthält, in den QML-Teil des Codes zu übertragen. Zum Beispiel eine solche Struktur.
struct Structure { int m_number; QString m_message; };
Es ist nur so, dass eine solche Struktur nicht auf QML übertragen werden kann, und das erste, was mir einfällt, ist, ein Signal zu machen sendToQml , das mehrere Argumente senden wird. Jedes Argument ist für ein bestimmtes Feld der Struktur verantwortlich.
void sendToQml(int number, QString message);
Dieses Signal befindet sich in Ihrer Klasse, die bestimmte Informationen mit Zahlen, Nachrichten usw. an QML sendet. Der Nachteil dieses Ansatzes liegt auf der Hand, da es viele solcher Felder geben kann und es dumm wäre, mit ein paar Dutzend Argumenten ein Signal zu setzen. Dieser Ansatz wird zum Senden kleiner Informationen nützlich sein, aber nicht für Datenstrukturen.
Es gibt eine weitere Möglichkeit, QVariantMap zu verwenden. Da es gut in ein QML-Array konvertiert wird, aus dem Sie Daten per Schlüssel entnehmen können. Ein Anwendungsbeispiel finden Sie in der offiziellen Qt-Dokumentation.
So werden Informationen in QML übernommen
// MyItem.qml Item { function readValues(anArray, anObject) { for (var i=0; i<anArray.length; i++) console.log("Array item:", anArray[i]) for (var prop in anObject) { console.log("Object item:", prop, "=", anObject[prop]) } } }
Und so werden diese Informationen zu C++ hinzugefügt
// C++ QQuickView view(QUrl::fromLocalFile("MyItem.qml")); QVariantList list; list << 10 << QColor(Qt::green) << "bottles"; QVariantMap map; map.insert("language", "QML"); map.insert("released", QDate(2010, 9, 21)); QMetaObject::invokeMethod(view.rootObject(), "readValues", Q_ARG(QVariant, QVariant::fromValue(list)), Q_ARG(QVariant, QVariant::fromValue(map)));
Aber ich mag diesen Ansatz nicht wirklich, weil eine Situation entstehen kann, in der Sie bestimmte Aktionen mit den Daten ausführen müssen, die die Struktur trägt, und QVariantMap kann solche Methoden im Prinzip nicht haben. Ja, und ich möchte eine ganzheitlichere Sicht auf Informationen als eine Reihe von Schlüsseln und Werten. Wenn dies eine Art Nachricht oder Datenobjekt ist, das innerhalb des C++-Codes übergeben und als integrales Objekt behandelt wird, warum sollten wir es dann in eine Reihe von Schlüssel-Wert-Paaren umwandeln, die in QVariantMap leben?
Das ist richtig, es ist optional. Tatsache ist, dass Sie von C ++ aus einen Zeiger auf ein Objekt der Klasse QObject an QML übergeben können. Und angesichts der Möglichkeiten von Polymorphismus , Vererbung usw. Sie können von der Klasse QObject erben und einen Zeiger auf dieses Objekt als Zeiger auf die Basisklasse QObject an QML übergeben. Darüber hinaus, ohne diese neue Klasse überhaupt als MetaType im Metaobjektsystem Qt zu registrieren.
Was wird es uns geben? Wir verstehen, dass Sie, wenn Sie von einer Klasse erben und der geerbten Klasse einige Methoden und Felder hinzufügen, mit ihnen arbeiten und sie aufrufen können, aber Sie benötigen einen Zeiger auf eine Objekt der entsprechenden Klasse, das heißt, wenn wir einen Zeiger auf die Basisklasse nehmen und ihm einen Zeiger auf die geerbte Klasse zuweisen, dann können wir innerhalb des Codes den Aufruf der Methode der geerbten Klasse beim Arbeiten nicht aufzeichnen mit dem Basisklassenzeiger. Das heißt, der folgende Code wird nicht kompiliert.
class A { public: A() {} }; class B : public A { public: B() {} void someMethod(); }; int main(int argc, char *argv[]) { A *a = new B(); // Ok. Можно присвоить указатель на наследованный класс указателю на базовый класс a->someMethod(); // Ошибка. А вот вызвать метод наследованного класса уже нельзя, базовый класс об это ничего не знает return 0; }
Und logischerweise macht es keinen Sinn, von QObject zu erben, ohne die Klasse über qmlRegisterType zu registrieren. Allerdings ist hier nicht alles so, wie wir es gewohnt sind. Der Punkt ist, dass, wenn eine Methode als Signal , slot oder Q_INVOKABLE -Methode oder eines der Felder markiert ist, dies der Fall ist als Q_PROPERTY markiert, dann kennt das Metaobjektsystem von Qt diese Methode oder dieses Feld bereits. Und die Methode kann beispielsweise über eine statische Klassenmethode QMetaObject aufgerufen werden. Dadurch können diese Methoden in QML aufgerufen werden, auch wenn es einen Zeiger auf die Basisklasse QObject gibt.
Sehen wir uns nun ein Beispiel einer Struktur und Klasse an, die von QObject geerbt und an QML übergeben werden.
Container.h
#ifndef CONTAINER_H #define CONTAINER_H #include <QObject> struct Structure : public QObject { explicit Structure(QObject *parent = nullptr); int m_number; QString m_message; private: Q_OBJECT // Сможем обращаться к полям из QML // параметр MEMBER указывает, что имеется возможность работать с этим полем и отслеживать его изменение в QML Q_PROPERTY(int number MEMBER m_number) Q_PROPERTY(QString message MEMBER m_message) public: // А также вызвать в QML этот метод Q_INVOKABLE QString getFullInfo() const; }; class Container : public QObject { Q_OBJECT // Сможем обращаться к полям из QML Q_PROPERTY(int number READ number WRITE setNumber NOTIFY numberChanged) Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) public: explicit Container(QObject *parent = nullptr); // А также вызвать в QML этот метод Q_INVOKABLE QString getFullInfo() const; int number() const; QString message() const; public slots: void setNumber(int number); void setMessage(QString message); signals: void numberChanged(int number); void messageChanged(QString message); private: int m_number; QString m_message; }; #endif // CONTAINER_H
Hier sehen wir die klassische Art, Felder und Methoden für ein benutzerdefiniertes Objekt, mit dem wir in QML arbeiten werden zu deklarieren. Das heißt, es gibt Makros Q_PROPERTY und Q_INVOKABLE sowie Signale und Slots.
Container.cpp
Der Inhalt der Quellcodedatei sollte überhaupt keine Fragen aufwerfen.
#include "Container.h" Structure::Structure(QObject *parent) : QObject(parent) { } QString Structure::getFullInfo() const { return QString("Full information from Structure %1").arg(m_number); } Container::Container(QObject *parent) : QObject(parent) { } QString Container::getFullInfo() const { return QString("Full information from Container %1").arg(m_number); } int Container::number() const { return m_number; } QString Container::message() const { return m_message; } void Container::setNumber(int number) { if (m_number == number) return; m_number = number; emit numberChanged(m_number); } void Container::setMessage(QString message) { if (m_message == message) return; m_message = message; emit messageChanged(m_message); }
Objektfabrik
Um das Erstellen von Objekten in C ++ - Code und das Übergeben eines Zeigers an QML zu vereinfachen, werde ich eine spezielle Factory erstellen, die im QML-Kontext registriert wird, sodass Sie schnell Strukturen und Container mit Informationen erstellen und einen Zeiger darauf zurückgeben können diese Objekte in QML.
Fabrik.h
#ifndef FACTORY_H #define FACTORY_H #include <QObject> class Factory : public QObject { Q_OBJECT public: explicit Factory(QObject *parent = nullptr); Q_INVOKABLE QObject* createContainer(); // Для создания контейнеров Q_INVOKABLE QObject* createStructure(); // Для создания структур private: int m_count {0}; int m_structureCount {0}; }; #endif // FACTORY_H
Factory.cpp
Sie werden vielleicht bemerken, dass ich das übergeordnete Element auf eine Fabrik eingestellt habe. Bei der Arbeit mit Zeigern und deren Übergabe von C++ an QML gibt es Nuancen bei der Eigentümerschaft von Zeigern. Wenn das Objekt kein übergeordnetes Objekt hat, kann es vom Garbage Collector in QML zerstört werden, als Ergebnis können wir in C ++ einen ungültigen Zeiger erhalten. Mehr in diesem Artikel .
#include "Factory.h" #include "Container.h" Factory::Factory(QObject *parent) : QObject(parent) { } QObject* Factory::createContainer() { Container* container = new Container(this); container->setNumber(++m_count); container->setMessage(QString("Container %1").arg(m_count)); return container; } QObject* Factory::createStructure() { Structure* structure = new Structure(this); structure->m_number = ++m_structureCount; structure->m_message = QString("Structure %1").arg(m_structureCount); return structure; }
main.cpp
Mal sehen, wie die Werksregistrierung im Kontext von QML aussieht. Dadurch können Sie überall im QML-Code auf die Fabrik zugreifen.
#include <QGuiApplication> #include <QQmlContext> #include <QQmlApplicationEngine> #include "Factory.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QLatin1String("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; Factory factory; engine.rootContext()->setContextProperty("factory", &factory); return app.exec(); }
main.qml
Nachdem alle vorbereitenden Operationen durchgeführt wurden, versuchen wir, mehrere Strukturen und Container zu erstellen und auf deren Felder und Methoden zu verweisen, um daraus Informationen abzuleiten.
import QtQuick 2.7 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 ApplicationWindow { visible: true width: 640 height: 480 title: qsTr("Hello World") // массив QML, в который можно поместить что угодно, в C++ это будет либо QVariantMap, либо QVariantList property var objectsArray: [] Text { id: textView clip: true anchors { top: parent.top left: parent.left right: parent.right bottom: parent.verticalCenter margins: 5 } } Button { id: addOBjectStructure text: qsTr("Add Structure") anchors { right: parent.horizontalCenter left: parent.left bottom: addOBjectButton.top margins: 5 } onClicked: { // Добавляем структуру в массив objectsArray.push(factory.createStructure()) } } Button { id: addOBjectButton text: qsTr("Add Object") anchors { right: parent.horizontalCenter left: parent.left bottom: parent.bottom margins: 5 } onClicked: { // Добавляем контейнер в массив objectsArray.push(factory.createContainer()) } } Button { text: qsTr("Read info from Objects") anchors { right: parent.right left: parent.horizontalCenter bottom: parent.bottom margins: 5 } onClicked: { // выводим текст из всех объектов массива textView.text = "" for (var i = 0; i < objectsArray.length; ++i) { // главное, чтобы все объекты имели методы с одинаковыми названиями var str = objectsArray[i].number + " " + objectsArray[i].message + "\n" + objectsArray[i].getFullInfo() + "\n" textView.text += str } } } }
Und wir werden Strukturen und Container im selben Array platzieren. Ich habe oben bereits gesagt, dass QVariantMap und QVariantList in QML in ein JavaScript-Array konvertiert werden, dem es egal ist, welche Informationen darin platziert werden. Wenn wir also versuchen, alle Elemente des Arrays zu durchlaufen und die Methoden number , message und getFullInfo() aufzurufen, werden wir keine Probleme haben. In diesem Fall werden alle diese Methoden implizit über die Methode QMetaObject::invokeMethod aufgerufen, und wenn die Methode registriert ist, wird sie aufgerufen. Und da das Array Objekte zweier verschiedener Klassen enthält, gilt hier vor allem, dass die Methoden den gleichen Namen haben müssen. Dies ist dem Verhalten der Enteneingabe in Python sehr ähnlich. Obwohl dies natürlich keine Ententypisierung ist. Dies ist eine Funktion des Meta-Objekt-Systems von Qt.
Das Ergebnis wird wie folgt sein
Отличный пример удобной работы с передачей данных из крестов.
Добрый день. Полезный урок, подскажите как можно передать структуру данных из qml в c++ при помощи QVariantMap. Со стороны qml упаковываю в Map, добавляю несколько пар ключ значение и вызываю матод(с++) в аргумент Q_INVOKABLE метода (c++) вставляю Map. В методе с++ QvariantMap пустой. Подскажите может что-то не так делаю.