Best Practice to Add Dynamic QML from C++ data
-
-
QML is not great at handling custom C++ objects that are not QObjects (see http://doc.qt.io/qt-5/qtqml-cppintegration-data.html). The easiest route is to save each basic element in its own role.
struct{ QString m_text; QFont m_textFont; }
can be replicated saving text in
Qt::UserRole
, and textFont inQt::UserRole+1
Where can I read up more on Roles?
http://doc.qt.io/qt-5/model-view-programming.html#basic-concepts scroll to "Item roles" paragraph and http://doc.qt.io/qt-5/qtquick-modelviewsdata-modelview.html#models
-
This is great, thanks!
But I have problem finding documentation on more complex User Roles. Could I implement a QAbstractListModel's data function to return a struct of data rather than a simple property for QML?
Example:
QVariant SomeListModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= mData.count()) return QVariant(); const SomeData &someData = mData[index.row()]; switch (role) { case HeaderText: //:: Simple Property Role returns a QString return someData.headerText(); break; case TextObj: //:: Return a more complex data structure, like a struct or class return someData.aDataStructOfData(); break; } return QVariant(); }
So 'aDataStructOfData' would return a struct or even a class of Qt types, like maybe a QString, a QFont etc I could later use to populate in QML?
-
take a look at http://qmlbook.github.io/en/ch15/index.html#models-in-c
Personally I had little luck integrating custom structs/classes as values in a model but I'm pretty sure it's just because I'm not that good rather than it being impossible
-
This is great, thanks!
But I have problem finding documentation on more complex User Roles. Could I implement a QAbstractListModel's data function to return a struct of data rather than a simple property for QML?
Example:
QVariant SomeListModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= mData.count()) return QVariant(); const SomeData &someData = mData[index.row()]; switch (role) { case HeaderText: //:: Simple Property Role returns a QString return someData.headerText(); break; case TextObj: //:: Return a more complex data structure, like a struct or class return someData.aDataStructOfData(); break; } return QVariant(); }
So 'aDataStructOfData' would return a struct or even a class of Qt types, like maybe a QString, a QFont etc I could later use to populate in QML?
So 'aDataStructOfData' would return a struct or even a class of Qt types, like maybe a QString, a QFont etc I could later use to populate in QML?
Yes it is possible. Make sure your struct or class uses
Q_OBJECT
macro. This class can then containQ_INVOKABLE
functions orQ_PROPERTY
's which will return yourQString
orQFont
. These propeties or functions defined as such can then be access from QML.Since
data
require aQVariant
you will require to return your class as aQVariant
. For that you may require you class or struct to be registered usingQ_DECLARE_METATYPE
.case TextObj: return QVariant::fromValue(someData.aDataStructOfData());
-
So 'aDataStructOfData' would return a struct or even a class of Qt types, like maybe a QString, a QFont etc I could later use to populate in QML?
Yes it is possible. Make sure your struct or class uses
Q_OBJECT
macro. This class can then containQ_INVOKABLE
functions orQ_PROPERTY
's which will return yourQString
orQFont
. These propeties or functions defined as such can then be access from QML.Since
data
require aQVariant
you will require to return your class as aQVariant
. For that you may require you class or struct to be registered usingQ_DECLARE_METATYPE
.case TextObj: return QVariant::fromValue(someData.aDataStructOfData());
@p3c0 Now I'm very interested here as on paper I see it happening but when I try and do it in practice I always fail.
for example:
TestData.h#ifndef TESTDATA_H #define TESTDATA_H #include <QString> #include <QMetaType> class TestData { Q_GADGET Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(QString desctiption READ desctiption WRITE setText) public: TestData(){} const QString& desctiption() const { return m_desctiption; } void setDesctiption(const QString &desctiption) { m_desctiption = desctiption; } const QString& text() const { return m_text; } void setText(const QString &text) { m_text = text; } private: QString m_text; QString m_desctiption; }; Q_DECLARE_METATYPE(TestData) #endif // TESTDATA_H
How would you use this as data in a model?
What I always did is put text in a role, description in another and use them as QStrings
-
@p3c0 Now I'm very interested here as on paper I see it happening but when I try and do it in practice I always fail.
for example:
TestData.h#ifndef TESTDATA_H #define TESTDATA_H #include <QString> #include <QMetaType> class TestData { Q_GADGET Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(QString desctiption READ desctiption WRITE setText) public: TestData(){} const QString& desctiption() const { return m_desctiption; } void setDesctiption(const QString &desctiption) { m_desctiption = desctiption; } const QString& text() const { return m_text; } void setText(const QString &text) { m_text = text; } private: QString m_text; QString m_desctiption; }; Q_DECLARE_METATYPE(TestData) #endif // TESTDATA_H
How would you use this as data in a model?
What I always did is put text in a role, description in another and use them as QStrings
With my rudimentary knowledge of QML I'd first register the type:
qmlRegisterType<TestData>("com.testns", 1, 0, "TestData");
And then you should be able to instantiate this in QML. However, I'm not convinced it'd work with gadgets, QML is pretty heavy on the
QObject
usage ... and honestly I don't know for sure this'd be enough for the type to be returned from a model. -
With my rudimentary knowledge of QML I'd first register the type:
qmlRegisterType<TestData>("com.testns", 1, 0, "TestData");
And then you should be able to instantiate this in QML. However, I'm not convinced it'd work with gadgets, QML is pretty heavy on the
QObject
usage ... and honestly I don't know for sure this'd be enough for the type to be returned from a model.@kshegunov said in Best Practice to Add Dynamic QML from C++ data:
And then you should be able to instantiate this in QML.
Exactly, instantiate is easy but if you put that thing in a model in C++ and call it in QML how can you tell QML your QVariant is TestData?
-
@kshegunov said in Best Practice to Add Dynamic QML from C++ data:
And then you should be able to instantiate this in QML.
Exactly, instantiate is easy but if you put that thing in a model in C++ and call it in QML how can you tell QML your QVariant is TestData?
@VRonin said in Best Practice to Add Dynamic QML from C++ data:
how can you tell QML your QVariant is TestData?
I don't follow. QML and Qt already know what is in the
QVariant
that's the purpose of declaring it as a meta type ... andqmlRegisterType
should expose the interface of said class to the QML engine, if I'm correct. -
ok, let's say our main looks like this:
#include <QApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <QStandardItemModel> #include "testdata.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); QStandardItemModel testModel; testModel.insertRows(0,2); testModel.insertColumn(0); TestData tempTestData; tempTestData.setText("Data1Text"); tempTestData.setDesctiption("Data1Desc"); testModel.setData(testModel.index(0,0),QVariant::fromValue(tempTestData)); tempTestData.setText("Data2Text"); tempTestData.setDesctiption("Data2Desc"); testModel.setData(testModel.index(1,0),QVariant::fromValue(tempTestData)); QQmlApplicationEngine engine; qmlRegisterType<TestData>("com.testns", 1, 0, "TestData"); engine.rootContext()->setContextProperty("testModel", &testModel); engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); return app.exec(); }
and main.qml is
import com.testns 1.0 ApplicationWindow { visible: true width: 640 height: 480 title: "Testing Model" ListView{ model: testModel delegate: ??? } }
What do you put in ??? to simply make text and description appear next to each others?
The official example http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html splits the internals on
Animal
into two different roles each containing a QString, I never saw an example of a delegate handling custom variant types -
ok, let's say our main looks like this:
#include <QApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <QStandardItemModel> #include "testdata.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); QStandardItemModel testModel; testModel.insertRows(0,2); testModel.insertColumn(0); TestData tempTestData; tempTestData.setText("Data1Text"); tempTestData.setDesctiption("Data1Desc"); testModel.setData(testModel.index(0,0),QVariant::fromValue(tempTestData)); tempTestData.setText("Data2Text"); tempTestData.setDesctiption("Data2Desc"); testModel.setData(testModel.index(1,0),QVariant::fromValue(tempTestData)); QQmlApplicationEngine engine; qmlRegisterType<TestData>("com.testns", 1, 0, "TestData"); engine.rootContext()->setContextProperty("testModel", &testModel); engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); return app.exec(); }
and main.qml is
import com.testns 1.0 ApplicationWindow { visible: true width: 640 height: 480 title: "Testing Model" ListView{ model: testModel delegate: ??? } }
What do you put in ??? to simply make text and description appear next to each others?
The official example http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html splits the internals on
Animal
into two different roles each containing a QString, I never saw an example of a delegate handling custom variant typesHaven't tried it, but I'd do something along the lines of:
ApplicationWindow { visible: true width: 640 height: 480 title: "Testing Model" ListView { model: testModel delegate: Rectangle { height: 25 width: 100 Text { id: tmText text: modelData.text anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom } Text { text: modelData.desctiption anchors.left: tmText.right anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom } } } }
-
@VRonin said in Best Practice to Add Dynamic QML from C++ data:
needs edit instead of modelData and it works! thanks so much
I'm glad it does. :D
I was just putting down the code to test it ... thanks for sparing me the trouble :P -
Okay I got it to work, here's what i got now, and it is a long post so bare with me, - I defined a class in the following way and mind you the QObject *parent in the constructor I always define as 0, I never send in anything there. Dunno if it is intended to do so.
class ComplexData : public QObject { Q_OBJECT Q_PROPERTY(QString text READ testString WRITE setTestString NOTIFY testStringChanged) public: explicit ComplexData(QObject *parent = 0); QString testString() const; void setTestString(const QString &testString); signals: void testStringChanged(QString); private: QString mTestString; };
Implementation of ComplexData.cpp
ComplexData::ComplexData(QObject *parent) : QObject(parent) { mTestString = "This is a test string from CPP!"; } QString ComplexData::testString() const { return mTestString; } void ComplexData::setTestString(const QString &testString) { mTestString = testString; emit testStringChanged(testString); }
My "SomeData" gets this new "ComplexData" as a member variable:
class SomeData { public: SomeData(const QString &headerText); QString headerText() const; ComplexData* complexData() const; private: QString mHeaderText; ComplexData* mComplexData; //:: Has to be a pointer for some reason };
Implementation of SomeData.cpp:
SomeData::SomeData(const QString &headerText) : mHeaderText(headerText) { mComplexData = new ComplexData(); //:: I just create the object here for Testing could be passed into Constructor though } ComplexData* SomeData::complexData() const { return mComplexData; }
And finally in my AbstractListModel 'SomeListModel.cpp' data function now looks like this:
QVariant SomeListModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= mSolutions.count()) return QVariant(); const SomeData &someData= mData[index.row()]; switch (role) { case HeaderText: return someData.headerText(); break; case ComplexData: return QVariant::fromValue( someData.complexData() ); break; } return QVariant(); }
With this I can use this in QML like:
Text { text: model.complexData.text }
Okay, all fine and dandy. I basically just moved some data to another class. I could've just kept on having this in the SomeData class. What I really need to know is if it is possible to return the whole "ComplexData" as a QML of type "Text", is that a QTextField type in CPP?
So instead in the data function of SomeListModel I would return say:
case ComplexData: return QVariant::fromValue( someData.complexData() ); //:: This returns a formatted textfield setup from C++ to be used in QML instead. break;
Not even sure if that is possible though. I am getting a bit confused here :/
-
@Placeable What do you mean by "formatted textfield setup" ?
-
@Placeable What do you mean by "formatted textfield setup" ?
@p3c0 said in Best Practice to Add Dynamic QML from C++ data:
@Placeable What do you mean by "formatted textfield setup" ?
In CPP I want to create a set of Texts to be used in QML - I read data from a JSON file and need to create Texts for QML dynamically. All data is stored on the CPP side so let's say I need to create a view with 3 Texts and another with 0 Texts. That is what I am struggling to do.
So I am wondering if it is possible to create these Texts in CPP (Whatever their CPP variant might be, I dunno) and then return this to be populated in said QML view by changing the QAbstractListModel class somehow.
-
@Placeable Do you mean something like dynamic object creation in QML ?
And in your case you want the QML component's code(Text
) will come from CPP ? -
@Placeable Do you mean something like dynamic object creation in QML ?
And in your case you want the QML component's code(Text
) will come from CPP ?@p3c0
Exactly so, dynamically. Basically I want to setup these Texts on the CPP side (Font size, formatting as such etc) and let QML now: Hey here is a Text for you to use, have fun! Oh by the way here's another Text to use for this ListItem. But that ListItem over there you will get no Text to use at all! Hah!So a ListItem could have a variable amount of Texts that I also need to somehow tell my View Delegate.
-
@Placeable I have done something similar my project here. So what that particular code does is it creates a
QQuickItem
from a base QML template as shown here. Then sets some color and font on it. So you can try to do something similar. But rememberQQmlComponent
requiresQQmlEngine
. This is the same with which you must have loaded the QML initially. Also remember that dynamicQQuickItem
also requires a visual parent which is set using setParent.