- 1. Container.h
- 2. Container.cpp
- 3. Зауыт нысаны
- 1. Factory.h
- 2. Factory.cpp
- 4. main.cpp
- 5. main.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 мета нысандары жүйесінің ерекшелігі.
Нәтиже келесідей болады
Отличный пример удобной работы с передачей данных из крестов.
Добрый день. Полезный урок, подскажите как можно передать структуру данных из qml в c++ при помощи QVariantMap. Со стороны qml упаковываю в Map, добавляю несколько пар ключ значение и вызываю матод(с++) в аргумент Q_INVOKABLE метода (c++) вставляю Map. В методе с++ QvariantMap пустой. Подскажите может что-то не так делаю.