Evgenii Legotckoi
Evgenii LegotckoiNov. 27, 2017, 2:42 a.m.

QML - Tutorial 034. Transfer of data structures from the C ++ application layer to the QML layer

One of the undoubted advantages of QML in Qt is that it allows a fairly sharp separation of backend logic from the application interface. That is, we write all the backend in C ++, and in QML we only display the required result.

In doing so, we can also write internal logic purely on QML, minimizing the presence of C ++ code, or vice versa, design the design of some elements in C ++, taking advantage of the OpenGL capabilities. Despite the distinction between QML and C ++ code parts on the backend and frontend applications, we do not have any big restrictions.

But sometimes it becomes necessary to transfer some data structure that will carry several fields with information, in the QML part of the code. For example, such a structure.

struct Structure 
{
    int m_number;
    QString m_message;
};

It's just that this structure can not be passed to QML and the first thing that comes to mind is to make the sendToQml signal, which will send out several arguments. Each argument will be responsible for a specific field structure.

void sendToQml(int number, QString message);

This signal will be located in your class, which will send to QML certain information with numbers, messages, etc. The minus of this approach is obvious, since there can be a lot of such fields and it will be foolish to make a signal with a couple of dozens of arguments. This approach will be useful for sending small information, but not for data structures.


There is another option to use QVariantMap. Because it is well-converted into an array of QML, from which you can collect data by key. An example of use is the official Qt documentation.

Thus, the information in QML

// 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])
        }
    }
}

And thus this information is added to C ++

// 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)));

But I do not particularly like this approach, since there can be a situation when it is necessary to do certain actions with data that the structure carries in itself, and QVariantMap, of such methods can not have in principle. Yes, and I want a more complete presentation of information than a set of keys and values. If this is some message or data object that is passed within the C ++ code and work happens to it as an integral object, then why should we turn it into a set of key-value pairs that live inside QVariantMap?

That's right, it's not necessary. The matter is that from C ++ it is possible to transfer in QML the pointer on object of class QObject . And given the possibilities of polymorphism , inheritance, etc. You can inherit from the QObject class and pass a pointer to this object in QML as a pointer to the QObject base class. Moreover, without even registering this new class as MetaType in the Qt meta-object system.

What will it give us? We understand that if you inherit from a class and add some methods and fields to the inherited class, you can work with them and call them, but you will need to have a pointer to the object of the corresponding class, that is, if we take pointer to the base class and assign it a pointer to the inherited class, then within the code we can not write a call to the method of the derived class when working with the base class pointer. That is, the following code is not compiled.

class A 
{
public:
    A() {}
};

class B : public A
{
public:
    B() {}
    void someMethod();
};

int main(int argc, char *argv[])
{
    A *a = new B();  // Ok. You can assign a pointer to an inherited class to a pointer to a base class
    a->someMethod(); // Error. But you can not call the method of the inherited class anymore, the base class does not know anything about it
    return 0;
}

And logically, there is no sense to inherit from QObject without registering the class via qmlRegisterType . However, everything is different here, as we are accustomed to. The fact is that if the method is marked as a signal, a slot or Q_INVOKABLE method or some of the fields is marked as Q_PROPERTY , then the meta-object system Qt already knows about this method or field. And the method, for example, can be called through the static method of the QMetaObject class. Due to this, these methods can be called in QML, even if there is a pointer to the base class QObject.

And now let's look at an example of the structure and class that are inherited from QObject, and which will be passed to QML.

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
    // We can access the fields from QML
    // the MEMBER parameter indicates that it is possible to work with this field and track its change in 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
    // We can access the fields from 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);

    // And also call this method in 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

Здесь мы видим классический вариант объявления полей и методов для кастомного объекта, с которым будет работать в QML . То есть имеются макросы Q_PROPERTY и Q_INVOKABLE , а также имеются сигналы и слоты.

Here we see a classic variant of declaring fields and methods for a custom object, which will work in QML . That is, there are macros Q_PROPERTY and Q_INVOKABLE , and there are signals and slots.

Container.cpp

The contents of the source file should not be called at all.

#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);
}

Factory of objects

For the convenience of creating objects in C ++ code and passing the pointer in QML, I will create a special factory that will be registered in the context of QML, which will quickly create Structures and Containers with information and return a pointer to these objects in QML.

Factory.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(); // To create containers
    Q_INVOKABLE QObject* createStructure(); // To create structures

private:
    int m_count {0};
    int m_structureCount {0};
};

#endif // FACTORY_H

Factory.cpp

You can notice that as a parent, I set up a factory. When working with pointers and passing them from C ++ to QML, there are nuances with pointer ownership. If the object is not exposed to parent, then it can be destroyed by the garbage collector in QML, eventually we can get an invalid pointer in C ++. Read more in this article .

#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

Let's see how the registration of the factory looks in the context of QML. This will allow you to access the factory anywhere in the QML code.

#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

After all the preparatory operations have been carried out, we will try to create several structures and containers and refer to their fields and methods to derive information from them.

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: {
            // Adding a structure to the array
            objectsArray.push(factory.createStructure())
        }
    }

    Button {
        id: addOBjectButton
        text: qsTr("Add Object")
        anchors {
            right: parent.horizontalCenter
            left: parent.left
            bottom: parent.bottom
            margins: 5
        }

        onClicked: {
            // Add the container to the array
            objectsArray.push(factory.createContainer())
        }
    }

    Button {
        text: qsTr("Read info from Objects")
        anchors {
            right: parent.right
            left: parent.horizontalCenter
            bottom: parent.bottom
            margins: 5
        }

        onClicked: {
            // output text from all array objects
            textView.text = ""
            for (var i = 0; i < objectsArray.length; ++i)
            {
                // the main thing is that all objects have methods with the same names
                var str = objectsArray[i].number + " " + objectsArray[i].message + "\n" + objectsArray[i].getFullInfo() + "\n"
                textView.text += str
            }
        }
    }
}

And we will place structures and containers in the same array. I already said above that QVariantMap and QVariantList are converted in QML into a JavaScript array, which does not care which information is placed in it. Therefore, when we try to go through all the elements of the array and call the methods number, message and getFullInfo() , we will not have any problems. In this case, all these methods will be implicitly called through the QMetaObject::invokeMethod method, and if the method is registered, it will be called. Because in the array objects of two different classes, the main thing here is that the methods should have the same name. This is very much like the behavior of duck typing in Python . Although of course this is not duck typing. This is a feature of the Meta-object system Qt .

The result will be as follows

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

BlinCT
  • Nov. 27, 2017, 6:12 a.m.

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

ilya.guzikov
  • May 20, 2021, 6:33 a.m.

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • April 26, 2024, 11:56 a.m.

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
d
  • dsfs
  • April 26, 2024, 11:45 a.m.

C++ - Test 002. Constants

  • Result:50points,
  • Rating points-4
d
  • dsfs
  • April 26, 2024, 11:35 a.m.

C++ - Test 001. The first program and data types

  • Result:73points,
  • Rating points1
Last comments
k
kmssrFeb. 9, 2024, 2:43 a.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVADec. 25, 2023, 6:30 p.m.
Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJoDec. 25, 2023, 4:38 p.m.
Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
GvozdikDec. 19, 2023, 5:01 a.m.
Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Now discuss on the forum
G
GarApril 22, 2024, 12:46 p.m.
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil AcademicsApril 20, 2024, 2:45 p.m.
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasovApril 14, 2024, 1:41 p.m.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел ДорофеевApril 14, 2024, 9:35 a.m.
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrexApril 4, 2024, 11:47 a.m.
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Follow us in social networks