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
OI

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

  • Result:40points,
  • Rating points-8
AD

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

  • Result:50points,
  • Rating points-4
m

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

  • Result:80points,
  • Rating points4
Last comments
ИМ
Игорь МаксимовNov. 22, 2024, 11:51 a.m.
Django - Tutorial 017. Customize the login page to Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiOct. 31, 2024, 2:37 p.m.
Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEOct. 19, 2024, 8:19 a.m.
Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовOct. 5, 2024, 7:51 a.m.
Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5July 5, 2024, 11:02 a.m.
QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiJune 24, 2024, 3:11 p.m.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Nov. 15, 2024, 6:04 a.m.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectJune 4, 2022, 3:49 a.m.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimOct. 25, 2024, 9:10 a.m.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Follow us in social networks