Evgenii Legotckoi
Evgenii LegotckoiҚар. 27, 2017, 2:42 Т.Ж.

QML - Сабақ 034. С++ қолданбалы деңгейінен QML деңгейіне деректер құрылымдарын беру

Qt-дегі QML-дің сөзсіз артықшылықтарының бірі - бұл бағдарлама интерфейсінен серверлік логиканы айтарлықтай күрт бөлуге мүмкіндік береді. Яғни, біз бүкіл серверді C ++ тілінде жазамыз, ал QML тілінде біз тек қажетті нәтижені көрсетеміз.

Сонымен қатар, біз C ++ кодының болуын барынша азайта отырып, ішкі логиканы тек QML тілінде жаза аламыз немесе керісінше OpenGL мүмкіндіктерін пайдалана отырып, C ++ тілінде кейбір элементтердің дизайнын белгілей аламыз. Кодтың QML және C++ бөліктерін backend және frontend қолданбаларына бөлуге қарамастан, бізде үлкен шектеулер жоқ.

Бірақ кейде кодтың QML бөлігіне ақпараты бар бірнеше өрістерді тасымалдайтын кейбір деректер құрылымын тасымалдау қажет болады. Мысалы, мұндай құрылым.

struct Structure 
{
    int m_number;
    QString m_message;
};

Мұндай құрылымды QML-ге көшіру мүмкін емес, бірінші ойға келетін нәрсе - бірнеше дәлелдер жіберетін сигнал sendToQml жасау. Әрбір аргумент құрылымның белгілі бір өрісіне жауапты болады.

void sendToQml(int number, QString message);

Бұл сигнал сіздің класыңызда орналасады, ол белгілі бір ақпаратты QML-ге сандармен, хабарламалармен және т.б. жібереді. Бұл тәсілдің кемшілігі анық, өйткені мұндай өрістер көп болуы мүмкін және бірнеше ондаған дәлелдермен сигнал беру ақымақтық болар еді. Бұл тәсіл деректер құрылымдары үшін емес, шағын ақпаратты жіберу үшін пайдалы болады.


QVariantMap пайдаланудың тағы бір мүмкіндігі бар. Ол QML массивіне жақсы түрлендірілгендіктен, деректерді кілт арқылы алуға болады. Қолдану мысалы ресми Qt құжаттамасында берілген.

Ақпарат 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])
        }
    }
}

Осылайша бұл ақпарат 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)));

Бірақ маған бұл тәсіл ұнамайды, себебі құрылым тасымалдайтын деректермен белгілі бір әрекеттерді орындау қажет болған жағдайда жағдай туындауы мүмкін және QVariantMap-те мұндай әдістер принципті түрде бола алмайды. Иә, мен кілттер мен мәндер жиынтығынан гөрі ақпараттың тұтас көрінісін қалаймын. Егер бұл C ++ кодының ішінде жіберілетін хабардың немесе деректер нысанының қандай да бір түрі болса және ол интегралдық нысан ретінде қарастырылса, онда неге біз оны QVariantMap ішінде тұратын кілт-мән жұптарының жиынына айналдыруымыз керек?

Дұрыс, бұл міндетті емес. Өйткені, C++ тілінен QObject класының объектісіне көрсеткішті QML-ге беруге болады. Және полиморфизм мүмкіндіктерін ескере отырып, мұрагерлік және т.б. сіз QObject сыныбынан мұра ала аласыз және осы нысанға көрсеткішті QObject базалық класына көрсеткіш ретінде QML -ге бере аласыз. Сонымен қатар, бұл жаңа классты Qt мета-нысан жүйесінде MetaType ретінде тіркемей-ақ.

Бұл бізге не береді? Егер сіз қандай да бір сыныптан мұра алсаңыз және мұраланған сыныпқа кейбір әдістер мен өрістерді қоссаңыз, олармен жұмыс істеуге және оларға қоңырау шалуға болатынын түсінеміз, бірақ сізде бір сыныпқа көрсеткіш болуы керек. сәйкес класстың объектісі, яғни егер базалық классқа көрсеткішті алып, оны мұраланған классқа көрсеткіш тағайындасақ, онда код ішінде біз жұмыс істегенде мұраланған класс әдісінің шақыруын жаза алмаймыз. базалық класс көрсеткішімен. Яғни, келесі код компиляцияланбайды.

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

Ал логикалық тұрғыдан алғанда, классты qmlRegisterType арқылы тіркемей, QObject мұрағатының мағынасы жоқ. Алайда мұнда бәрі біз үйренген әдіс емес. Мәселе мынада, егер әдіс сигнал , слот немесе Q_INVOKABLE әдісі немесе кез келген өріс ретінде белгіленсе Q_PROPERTY ретінде белгіленген болса, Qt мета-нысан жүйесі бұл әдіс немесе өріс туралы бұрыннан біледі. Ал әдісті, мысалы, QMetaObject класының статикалық әдісі арқылы шақыруға болады. Осыған байланысты, QObject базалық класының көрсеткіші болса да, бұл әдістерді QML-де шақыруға болады.

Ал енді QObject-тен мұраланған және 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
    // Сможем обращаться к полям из 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

Мұнда біз [ QML ішінде жұмыс істейтін теңшелетін нысан] үшін өрістер мен әдістерді жариялаудың классикалық әдісін көреміз (https://evileg.com/post/296/). Яғни, Q_PROPERTY және Q_INVOKABLE макростары, сондай-ақ сигналдар мен слоттар бар.

Container.cpp

Бастапқы код файлының мазмұны ешқандай сұрақ тудырмауы керек.

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

Зауыт нысаны

C ++ кодында объектілерді құру және QML-ге көрсеткішті беру ыңғайлылығы үшін мен QML контекстінде тіркелетін арнайы зауыт жасаймын, ол ақпаратпен құрылымдар мен контейнерлерді жылдам жасауға және көрсеткішті қайтаруға мүмкіндік береді. бұл нысандар 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(); // Для создания контейнеров
    Q_INVOKABLE QObject* createStructure(); // Для создания структур

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

#endif // FACTORY_H

Factory.cpp

Менің ата-ананы зауытқа орнатқанымды байқайсыз. Көрсеткіштермен жұмыс істегенде және оларды C++ тілінен QML-ге бергенде, көрсеткішті иеленумен нюанстар бар. Егер нысанның ата-анасы болмаса, онда оны QML-де қоқыс жинаушы жоюы мүмкін, нәтижесінде біз C ++ тілінде жарамсыз көрсеткішті аламыз. Толығырақ осы мақалада .

#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

QML контекстінде зауытты тіркеу қалай көрінетінін көрейік. Бұл QML кодының кез келген жерінде зауытқа кіруге мүмкіндік береді.

#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

Барлық дайындық операцияларын орындағаннан кейін, бірнеше құрылымдар мен контейнерлерді жасауға тырысайық және олардан ақпарат алу үшін олардың өрістері мен әдістеріне сілтеме жасайық.

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
            }
        }
    }
}

Біз құрылымдар мен контейнерлерді бір массивке орналастырамыз. Мен QVariantMap және QVariantList QML тілінде JavaScript массивіне түрлендірілетінін жоғарыда айттым, бұл оған қандай ақпарат орналастырылғанына мән бермейді. Сондықтан, біз массивтің барлық элементтерін қайталауға тырысқанда және number , хабарлама және getFullInfo() әдістерін шақырғанда, бізде ешқандай проблемалар болмайды. Бұл жағдайда барлық осы әдістер QMetaObject::invokeMethod әдісі арқылы шақырылады, ал егер әдіс тіркелген болса, онда ол шақырылады. Ал массив екі түрлі кластың объектілерін қамтитындықтан, мұнда ең бастысы әдістердің атаулары бірдей болуы керек. Бұл Python тіліндегі үйрек теру әрекетіне өте ұқсас. Әрине, бұл үйрек теру емес. Бұл Qt мета нысандары жүйесінің ерекшелігі.

Нәтиже келесідей болады

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

BlinCT
  • Қар. 27, 2017, 6:12 Т.Ж.

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

ilya.guzikov
  • Мамыр 20, 2021, 6:33 Т.Ж.

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

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
Г

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:66ұпай,
  • Бағалау ұпайлары-1
t

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:33ұпай,
  • Бағалау ұпайлары-10
t

Qt - Тест 001. Сигналы и слоты

  • Нәтиже:52ұпай,
  • Бағалау ұпайлары-4
Соңғы пікірлер
G
GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrАқп. 8, 2024, 6:43 Т.Қ.
Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCTМаусым 25, 2024, 1 Т.Ж.
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCTМамыр 5, 2024, 5:46 Т.Ж.
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

Бізді әлеуметтік желілерде бақылаңыз