Privacy policyContactsAbout siteOpinionsGitHubDonate
© EVILEG 2015-2018
Recommend hosting
TIMEWEB

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

QML, Qt, Meta, Q_INVOKABLE

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

Comment

Nov. 27, 2017, 11:12 a.m.

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

Comment

Only authorized users can write comments.
Sign in or Register, Please
Nov. 16, 2018, 7:09 p.m.
Илья Завьялов

Qt - Test 001. Signals and slots

  • Result:31points,
  • Rating scores-10
Nov. 16, 2018, 12:49 p.m.
Ирина Минигузина

C++ - Тест 003. Условия и циклы

  • Result:0points,
  • Rating scores-10
Nov. 16, 2018, 8:55 a.m.
Vitaliy

Qt - Test 001. Signals and slots

  • Result:52points,
  • Rating scores-4
Recent comments
Nov. 16, 2018, 6:50 a.m.
Евгений Легоцкой

Добрый день! шаблон не находит, или шаблон неправильно прописали, или тег шаблона неправильно написан, иных выводов сделать не могу, из того, что вы написали. трейсбек нужно смотреть. Со...
Nov. 16, 2018, 6:48 a.m.
Евгений Легоцкой

пройтись циклом по всем виджетам в обратном порядке for (int i = ui->vertialLayout->count() - 1; i >= 0; --i){ QWidget* w = ui->verticalLyout->itemAt(i)->widget();...
Nov. 15, 2018, 9:35 p.m.
chunk

Доброго времени суток Евгений. Не подскажете что я делаю не так? Получаю ошибку такого характера: Reverse for 'add_comment' with arguments '('',)' not found. 1 pattern(s) tried: ...
Nov. 15, 2018, 3:35 p.m.
Михаиллл

Спасибо. Похоже где то описку сделал, поэтому не работало. Я добавил на verticalLayout много виджитов. А можно ли их как то быстро и просто удалить?
Nov. 15, 2018, 2:55 p.m.
Евгений Легоцкой

verticalLayout - это, по-моему предположению, должен быть у вас объект класса QVBoxLayout, который наследован от QBoxLayout. Поэтому открываете документацию на QVBoxLayout ...
Now discuss on the forum
Nov. 17, 2018, 11:20 p.m.
Евгений Легоцкой

Три года назад я подключал ffmpeg в проект на Qt/С++ на первой работе. И кодирование декодирование удалось запустить. Подключал как библиотеку. Думаю, что в зависимости от проекта мо...
Nov. 17, 2018, 2:13 p.m.
Чарльз Грин

Я сейчас занимаюсь мультимедией, это аудио и видео, программы для концертов и т. д. Бибилиотека Qt очень помогает, она надежная, а надежность в моих приложениях очень важный фактор чтоб не пол...
Nov. 16, 2018, 4:28 p.m.
Евгений Легоцкой

Добрый день! Спасибо, что воспользовались именно форумом. Заниматься курсовыми работами чьими-то ни было у меня времени нет, у самого полторы работы. Но что-то подсказать на фо...
Nov. 16, 2018, 9:52 a.m.
Евгений Легоцкой

Отладчик!!!! Версия комплекта MSVC 2015 + компилятор 14.0!!!!
Join us in social networks

For registered users on the site there is a minimum amount of advertising