accessing aggregates (QVector of a struct)
-
@fcarney my console.log isn't working for this app, so I can't tell you. I discovered Q_GADGET from some online searching; in simplest terms, it's a lightweight version of Q_OBJECT (no signals/slots).
I'm not at all concerned with updating anything other than my display. But now you have me wondering where I should really define all those values. (I would rather not use JSON, but that's probably the right way to do this.)
-
@kshegunov the "name" field isn't accessible as I've currently implemented it. I think I may have over-designed this. I've made several changes since my earlier posts, so let me recap my code:
The struct:
struct BottleData { Q_GADGET public: uint32_t m_volume; // amount in bottle (in uL) uint32_t m_amountNeeded; // amount needed for synth (in uL) int m_position; // still figuring this one out std::string m_name; // name of the reagent ReagentBottleType m_bottleType; // bottle type. Q_PROPERTY(uint32_t volume MEMBER m_volume) Q_PROPERTY(uint32_t amountNeeded MEMBER m_amountNeeded) Q_PROPERTY(int position MEMBER m_position) Q_PROPERTY(std::string name MEMBER m_name) Q_PROPERTY(ReagentBottleType bottleType MEMBER m_bottleType) }; Q_DECLARE_METATYPE(BottleData)
The class:
typedef QVector<BottleData> BottleDataList; class BottleList : public QObject { Q_OBJECT private: BottleDataList m_bottleList; public: explicit BottleList(QObject *parent = nullptr); Q_PROPERTY(QVariantList qvl READ getBottleListQv) QVariantList getBottleListQv(); ...
From reading the docs, I was under the impression that I wouldn't need a getter for the fields in the struct, because I used the MEMBER macro. Did I misinterpret this?
@mzimmers said in accessing aggregates (QVector of a struct):
Q_DECLARE_METATYPE(BottleData)
is already done by the
Q_GADGET
so it's superfluous.Switch
std::string m_name;
to
QString
.Register the type with
QML
(qmlRegisterType
) if you intend to create instances of it from there.From reading the docs, I was under the impression that I wouldn't need a getter for the fields in the struct, because I used the MEMBER macro. Did I misinterpret this?
Nope, this is correct as far as I recall.
If I use the repeater to load the names, can I alter the individual bottles afterwards?
I don't think so, but I'm a noobster with QML. I believe you can imperatively create the items like this (untested):
Component { id: component Bottle { cellText: "default text" } onCompleted: changeConsumablesViewModel.getBottleListQv().forEach(element => function(element) { createObject(parentItemId, { cellText: element.name }); }, this); }
or something akin.
-
OK, so why doesn't this work?
Rectangle { id: rack function getBottleName(i) { return changeConsumablesViewModel.getBottleListQv()[i].m_name } Bottle { cellText: rack.getBottleName(0)// "W7" }
I get this error on the line with the "return" statement:
TypeError: Cannot read property 'm_name' of undefined
-
OK, so why doesn't this work?
Rectangle { id: rack function getBottleName(i) { return changeConsumablesViewModel.getBottleListQv()[i].m_name } Bottle { cellText: rack.getBottleName(0)// "W7" }
I get this error on the line with the "return" statement:
TypeError: Cannot read property 'm_name' of undefined
@mzimmers said in accessing aggregates (QVector of a struct):
TypeError: Cannot read property 'm_name' of undefined
Your property is called
name
, also check the elements you get in that array. Side note: you may need to wait for the component to be fully loaded before doing that (but take with a grain of salt). -
@mzimmers said in accessing aggregates (QVector of a struct):
TypeError: Cannot read property 'm_name' of undefined
Your property is called
name
, also check the elements you get in that array. Side note: you may need to wait for the component to be fully loaded before doing that (but take with a grain of salt).@kshegunov said in accessing aggregates (QVector of a struct):
@mzimmers said in accessing aggregates (QVector of a struct):
TypeError: Cannot read property 'm_name' of undefined
Your property is called
name
, also check the elements you get in that array.Tried with "name" -- same error.
Side note: you may need to wait for the component to be fully loaded before doing that (but take with a grain of salt).
Actually, I think you're on to it here. For some reason, the people who wrote this app (I'm just maintaining it) load all the QML files up-front, rather than as-needed. I think the problem is that this is an empty vector when this function is first called...I need to think of how best to handle that.
-
@kshegunov said in accessing aggregates (QVector of a struct):
@mzimmers said in accessing aggregates (QVector of a struct):
TypeError: Cannot read property 'm_name' of undefined
Your property is called
name
, also check the elements you get in that array.Tried with "name" -- same error.
Side note: you may need to wait for the component to be fully loaded before doing that (but take with a grain of salt).
Actually, I think you're on to it here. For some reason, the people who wrote this app (I'm just maintaining it) load all the QML files up-front, rather than as-needed. I think the problem is that this is an empty vector when this function is first called...I need to think of how best to handle that.
@mzimmers said in accessing aggregates (QVector of a struct):
Actually, I think you're on to it here. For some reason, the people who wrote this app (I'm just maintaining it) load all the QML files up-front, rather than as-needed. I think the problem is that this is an empty vector when this function is first called...I need to think of how best to handle that.
Check that through the
console
. If you want, you can try to wait for the component by adding[1]:Component.onCompleted: <js code to do w/e>
[1]: https://doc.qt.io/qt-5/qml-qtqml-component.html#completed-signal
-
Okay, I tested it. I cannot get Q_GADGET to work so I used Q_OBJECT:
class in main.cpp:using ReagentBottleType = int; class BottleData : public QObject { Q_OBJECT Q_PROPERTY(int volume MEMBER m_volume NOTIFY somethingChanged) Q_PROPERTY(int amountNeeded MEMBER m_amountNeeded NOTIFY somethingChanged) Q_PROPERTY(int position MEMBER m_position NOTIFY somethingChanged) Q_PROPERTY(std::string name MEMBER m_name NOTIFY somethingChanged) Q_PROPERTY(ReagentBottleType bottleType MEMBER m_bottleType NOTIFY somethingChanged) public: BottleData(QObject* parent=nullptr) : QObject(parent) { connect(this, &BottleData::somethingChanged, [this](){ qDebug() << "somethingChanged" << m_position << QString::fromStdString(m_name) << m_volume; }); } int m_volume=0; // amount in bottle (in uL) int m_amountNeeded=0; // amount needed for synth (in uL) int m_position=0; // still figuring this one out std::string m_name=""; // name of the reagent int m_bottleType=0; // bottle type. signals: void somethingChanged(); };
setting contextProperty in main.cpp for testing:
TestObj testobj; auto context = engine.rootContext(); context->setContextProperty("varlisttestobj", &testobj);
QML to exercise the object in C++:
Column { anchors.top: listview1.bottom Repeater { model: varlisttestobj.varList Row { id: bottledelegate width: 50 spacing: 20 Component.onCompleted: console.log(modelData, modelData.position, modelData.volume) Timer { interval: 1000 repeat: true running: true onTriggered: { modelData.volume += 1 } } Text { text: modelData.position height: 20 } Text { text: modelData.volume height: 20 } } } }
QML did not like uint32_t at all. So you will have to find another type that it likes on that page with compatible QML types I linked earlier.
-
Oops, I forgot test object in main.cpp:
class TestObj : public QObject { Q_OBJECT Q_PROPERTY(QVariantList varList READ varList NOTIFY varListChanged) //Q_PROPERTY(QObjectList objList READ objList NOTIFY objListChanged) public: TestObj(QObject* parent=nullptr) : QObject(parent) { for(int count=0; count<10; ++count){ auto bottle = new BottleData(); bottle->m_volume = count*10; bottle->m_position = count; m_bottleData.append(bottle); } emit varListChanged(); } QVariantList varList(){ QVariantList list; for(auto bottleData: qAsConst(m_bottleData)){ list.append(QVariant::fromValue(bottleData)); } return list; } /* QObjectList objList(){ QObjectList list; for(auto bottleData: qAsConst(m_bottleData)){ list.append(bottleData); } return list; } */ signals: void varListChanged(); private: QVector<BottleData*> m_bottleData; };
-
Okay, so here you go:
types.h
#ifndef TYPES_H #define TYPES_H #include <QObject> #include <QVector> #include <QVariant> #include <QVariantList> struct Bottle { Q_GADGET Q_PROPERTY(QString name MEMBER m_name) Q_PROPERTY(qreal size MEMBER m_size) public: QString m_name; qreal m_size; }; class DataSource : public QObject { Q_OBJECT public: Q_INVOKABLE QVariantList getData(); }; #endif // TYPES_H
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "types.h" QVariantList DataSource::getData() { return { QVariant::fromValue<Bottle>({ "First bottle", 0.75 }), QVariant::fromValue<Bottle>({ "Second bottle", 0.70 }) }; } int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.addImportPath(QStringLiteral("qrc:/")); QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url] (QObject * obj, const QUrl & objUrl) -> void { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); DataSource source; engine.rootContext()->setContextProperty(QStringLiteral("DataSource"), &source); qmlRegisterUncreatableType<Bottle>("Example", 1, 0, "Bottle", ""); engine.load(url); return app.exec(); }
main.qml
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 import Example 1.0 Window { id: mainWindow visible: true width: 300 height: 200 title: qsTr("Some title") Component { id: template Text { text: "defaultText" } } Column { Component.onCompleted: DataSource.getData().forEach(function(element) { template.createObject(this, { text: element.name }); }, this) } }
PS. QML is damn annoying ...
-
Okay, so here you go:
types.h
#ifndef TYPES_H #define TYPES_H #include <QObject> #include <QVector> #include <QVariant> #include <QVariantList> struct Bottle { Q_GADGET Q_PROPERTY(QString name MEMBER m_name) Q_PROPERTY(qreal size MEMBER m_size) public: QString m_name; qreal m_size; }; class DataSource : public QObject { Q_OBJECT public: Q_INVOKABLE QVariantList getData(); }; #endif // TYPES_H
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "types.h" QVariantList DataSource::getData() { return { QVariant::fromValue<Bottle>({ "First bottle", 0.75 }), QVariant::fromValue<Bottle>({ "Second bottle", 0.70 }) }; } int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.addImportPath(QStringLiteral("qrc:/")); QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url] (QObject * obj, const QUrl & objUrl) -> void { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); DataSource source; engine.rootContext()->setContextProperty(QStringLiteral("DataSource"), &source); qmlRegisterUncreatableType<Bottle>("Example", 1, 0, "Bottle", ""); engine.load(url); return app.exec(); }
main.qml
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 import Example 1.0 Window { id: mainWindow visible: true width: 300 height: 200 title: qsTr("Some title") Component { id: template Text { text: "defaultText" } } Column { Component.onCompleted: DataSource.getData().forEach(function(element) { template.createObject(this, { text: element.name }); }, this) } }
PS. QML is damn annoying ...
@kshegunov said in accessing aggregates (QVector of a struct):
PS. QML is damn annoying ...
Heh...no argument there, but if I'm reading the tea leaves correctly, it's here to stay.
I understand your C++ code, but I'm still trying to figure out what your QML results in. What exactly is this doing?
Component { id: template Text { text: "defaultText" } } Column { Component.onCompleted: DataSource.getData().forEach(function(element) { template.createObject(this, { text: element.name }); }, this) }
-
@kshegunov said in accessing aggregates (QVector of a struct):
PS. QML is damn annoying ...
Heh...no argument there, but if I'm reading the tea leaves correctly, it's here to stay.
I understand your C++ code, but I'm still trying to figure out what your QML results in. What exactly is this doing?
Component { id: template Text { text: "defaultText" } } Column { Component.onCompleted: DataSource.getData().forEach(function(element) { template.createObject(this, { text: element.name }); }, this) }
@mzimmers said in accessing aggregates (QVector of a struct):
I understand your C++ code, but I'm still trying to figure out what your QML results in. What exactly is this doing?
Well, I'm leveraging the fact that the QML engine is a glorified object factory. You know the widgets' basics, so with QtQuick it's the same story, more or less. It goes roughly like this:
- All visual items are derived from
QQuickItem
, these include the stuff you see in the QML file definition likeText
andColumn
. Component
does not(!) derive fromQQuickItem
, it is aQObject
(or ratherQQmlComponent
) that's supposed to create yourQQuickItem
s by callingQQmlComponent::createObject
and passing it the correct visual parent and maybe a set of property values.- When you load a QML file, this is what the engine does - it reads and instantiates the component your file represents, hence it creates all the visual and non-visual items described in said file. It parents everything by the way the items are nested (think of it as a big
QObject
tree). All the visual items are (re)parented to the rootQQuickItem
so they get painted correctly. - After the object tree's loaded and the objects are instantiated you get the
completed
signal emitted and propagated through your items (visual and non-visual), so you can do w/e. - In the above code you attach to the
completed
signal, then you call thegetData
to retrieve the list of structs, thenforEach
of the elements of the array you execute the anonymous function. The function is a dummy mostly. It just needs to go get the reference to thetemplate
component (which isn't a visual item, as mentioned) and call it'screateObject
to create theText
item contained. The first argument of the method is the visual parent (which is theColumn
in this case) and the second parameter is the set of properties to pass along, which is just the text value retrieved from the structure.
PS. This code's tested (unlike most of the snippets I provide) so you can directly plug it in your project/app and play with it as you wish.
- All visual items are derived from
-
@mzimmers said in accessing aggregates (QVector of a struct):
I understand your C++ code, but I'm still trying to figure out what your QML results in. What exactly is this doing?
Well, I'm leveraging the fact that the QML engine is a glorified object factory. You know the widgets' basics, so with QtQuick it's the same story, more or less. It goes roughly like this:
- All visual items are derived from
QQuickItem
, these include the stuff you see in the QML file definition likeText
andColumn
. Component
does not(!) derive fromQQuickItem
, it is aQObject
(or ratherQQmlComponent
) that's supposed to create yourQQuickItem
s by callingQQmlComponent::createObject
and passing it the correct visual parent and maybe a set of property values.- When you load a QML file, this is what the engine does - it reads and instantiates the component your file represents, hence it creates all the visual and non-visual items described in said file. It parents everything by the way the items are nested (think of it as a big
QObject
tree). All the visual items are (re)parented to the rootQQuickItem
so they get painted correctly. - After the object tree's loaded and the objects are instantiated you get the
completed
signal emitted and propagated through your items (visual and non-visual), so you can do w/e. - In the above code you attach to the
completed
signal, then you call thegetData
to retrieve the list of structs, thenforEach
of the elements of the array you execute the anonymous function. The function is a dummy mostly. It just needs to go get the reference to thetemplate
component (which isn't a visual item, as mentioned) and call it'screateObject
to create theText
item contained. The first argument of the method is the visual parent (which is theColumn
in this case) and the second parameter is the set of properties to pass along, which is just the text value retrieved from the structure.
PS. This code's tested (unlike most of the snippets I provide) so you can directly plug it in your project/app and play with it as you wish.
@kshegunov OK, I think I understand 1-4. But where is the getData() function explicitly referenced/called? (I think your getData() is the equivalent to my QVariantList BottleList::getBottleListQv().)
I still don't understand how to retrieve the information to use it in my bottle objects.
Bottle { id: bottle1 cellX: 25 cellY: 105 cellHeight: 75 cellWidth: 75 bottleScaleFactor: scaleFactor cellText: "W7" // how to replace this with something from getData()? cellColor: "red" }
Thanks...
- All visual items are derived from
-
@kshegunov OK, I think I understand 1-4. But where is the getData() function explicitly referenced/called? (I think your getData() is the equivalent to my QVariantList BottleList::getBottleListQv().)
I still don't understand how to retrieve the information to use it in my bottle objects.
Bottle { id: bottle1 cellX: 25 cellY: 105 cellHeight: 75 cellWidth: 75 bottleScaleFactor: scaleFactor cellText: "W7" // how to replace this with something from getData()? cellColor: "red" }
Thanks...
hi
@mzimmers said in accessing aggregates (QVector of a struct):
I still don't understand how to retrieve the information to use it in my bottle objects.
the answer is
@kshegunov said in accessing aggregates (QVector of a struct):
In the above code you attach to the completed signal, then you call the getData to retrieve the list of structs, then forEach of the elements of the array you execute the anonymous function. The function is a dummy mostly. It just needs to go get the reference to the template component (which isn't a visual item, as mentioned) and call it's createObject to create the Text item contained. The first argument of the method is the visual parent (which is the Column in this case) and the second parameter is the set of properties to pass along, which is just the text value retrieved from the structure.
Column { Component.onCompleted: DataSource.getData().forEach(function(element) {//anonymous function call for each object returned by getData() template.createObject(this, { text: element.name }); // create a qml object using the template and pass the value retrieved from the structure ( text:element.name ) }, this) }
Please make an empty project and test one of the examples given by @kshegunov @fcarney or me
-
hi
@mzimmers said in accessing aggregates (QVector of a struct):
I still don't understand how to retrieve the information to use it in my bottle objects.
the answer is
@kshegunov said in accessing aggregates (QVector of a struct):
In the above code you attach to the completed signal, then you call the getData to retrieve the list of structs, then forEach of the elements of the array you execute the anonymous function. The function is a dummy mostly. It just needs to go get the reference to the template component (which isn't a visual item, as mentioned) and call it's createObject to create the Text item contained. The first argument of the method is the visual parent (which is the Column in this case) and the second parameter is the set of properties to pass along, which is just the text value retrieved from the structure.
Column { Component.onCompleted: DataSource.getData().forEach(function(element) {//anonymous function call for each object returned by getData() template.createObject(this, { text: element.name }); // create a qml object using the template and pass the value retrieved from the structure ( text:element.name ) }, this) }
Please make an empty project and test one of the examples given by @kshegunov @fcarney or me
@LeLev (et al): kshugenov's example does indeed work. I tried modifying it as follows:
Column { property var myArray: [] Component.onCompleted: DataSource.getData().forEach(function(bottle) { // template.createObject(this, { text: bottle.name }); myArray.push(bottle.name) }, this) Text { text: myArray[0] }
But I get a runtime error that myArray is not defined. Am I supposed to use something other than a property var for this?
Thanks...
-
@LeLev (et al): kshugenov's example does indeed work. I tried modifying it as follows:
Column { property var myArray: [] Component.onCompleted: DataSource.getData().forEach(function(bottle) { // template.createObject(this, { text: bottle.name }); myArray.push(bottle.name) }, this) Text { text: myArray[0] }
But I get a runtime error that myArray is not defined. Am I supposed to use something other than a property var for this?
Thanks...
@mzimmers said in accessing aggregates (QVector of a struct):
myArray is not defined
you need to assign the id of Column so you can access its properties from outside, it is qml basics
Column {
id :col
property var myArray: []
Text {
text: col.myArray[0]
}
}@mzimmers said in accessing aggregates (QVector of a struct):
kshugenov's example does indeed work.
it does almost exactly the same thing as my very first example/answer.
-
@mzimmers said in accessing aggregates (QVector of a struct):
myArray is not defined
you need to assign the id of Column so you can access its properties from outside, it is qml basics
Column {
id :col
property var myArray: []
Text {
text: col.myArray[0]
}
}@mzimmers said in accessing aggregates (QVector of a struct):
kshugenov's example does indeed work.
it does almost exactly the same thing as my very first example/answer.
@LeLev ah. So, now my code looks like this:
Column { id: myColumn property var myArray: [] Component.onCompleted: DataSource.getData().forEach(function(bottle) { // template.createObject(this, { text: bottle.name }); myArray.push(bottle.name) }, this) Text { text: myColumn.myArray[0] } Text { text:myColumn.myArray[1] } Text { text: myColumn.myArray[2] } }
and I'm getting this error: "Unable to assign [undefined] to QString"
EDIT: I've stepped through this in the debugger, and the array is most definitely being created.
-
@LeLev ah. So, now my code looks like this:
Column { id: myColumn property var myArray: [] Component.onCompleted: DataSource.getData().forEach(function(bottle) { // template.createObject(this, { text: bottle.name }); myArray.push(bottle.name) }, this) Text { text: myColumn.myArray[0] } Text { text:myColumn.myArray[1] } Text { text: myColumn.myArray[2] } }
and I'm getting this error: "Unable to assign [undefined] to QString"
EDIT: I've stepped through this in the debugger, and the array is most definitely being created.
@mzimmers said in accessing aggregates (QVector of a struct):
Column {
id: myColumn
property var myArray: []
Component.onCompleted: DataSource.getData().forEach(function(bottle) {
// template.createObject(this, { text: bottle.name });
myArray.push(bottle.name)
}, this)you should not modify myArray directly in the function but create a local array then copy its content into myArray
Column { id: myColumn property var myArray: [] Component.onCompleted: { var arr = [] DataSource.getData().forEach(function(bottle) { //myArray.push(bottle.name) arr.push(bottle.name) }, this); myColumn.myArray = arr; }
-
@mzimmers said in accessing aggregates (QVector of a struct):
Column {
id: myColumn
property var myArray: []
Component.onCompleted: DataSource.getData().forEach(function(bottle) {
// template.createObject(this, { text: bottle.name });
myArray.push(bottle.name)
}, this)you should not modify myArray directly in the function but create a local array then copy its content into myArray
Column { id: myColumn property var myArray: [] Component.onCompleted: { var arr = [] DataSource.getData().forEach(function(bottle) { //myArray.push(bottle.name) arr.push(bottle.name) }, this); myColumn.myArray = arr; }