Evgenii Legotckoi
Evgenii Legotckoi27. November 2017 02:42

QML - Tutorial 034. Übertragung von Datenstrukturen aus der C++ Anwendungsschicht in die QML Schicht

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

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

BlinCT
  • 27. November 2017 06:12

Отличный пример удобной работы с передачей данных из крестов.

ilya.guzikov
  • 20. Mai 2021 06:33

Добрый день. Полезный урок, подскажите как можно передать структуру данных из qml в c++ при помощи QVariantMap. Со стороны qml упаковываю в Map, добавляю несколько пар ключ значение и вызываю матод(с++) в аргумент Q_INVOKABLE метода (c++) вставляю Map. В методе с++ QvariantMap пустой. Подскажите может что-то не так делаю.

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25. Dezember 2023 10:30
Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken