accessing aggregates (QVector of a struct)
-
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_Hmain.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 likeTextandColumn. Componentdoes not(!) derive fromQQuickItem, it is aQObject(or ratherQQmlComponent) that's supposed to create yourQQuickItems by callingQQmlComponent::createObjectand 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
QObjecttree). All the visual items are (re)parented to the rootQQuickItemso they get painted correctly. - After the object tree's loaded and the objects are instantiated you get the
completedsignal emitted and propagated through your items (visual and non-visual), so you can do w/e. - In the above code you attach to the
completedsignal, then you call thegetDatato retrieve the list of structs, thenforEachof 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 thetemplatecomponent (which isn't a visual item, as mentioned) and call it'screateObjectto create theTextitem contained. The first argument of the method is the visual parent (which is theColumnin 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 likeTextandColumn. Componentdoes not(!) derive fromQQuickItem, it is aQObject(or ratherQQmlComponent) that's supposed to create yourQQuickItems by callingQQmlComponent::createObjectand 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
QObjecttree). All the visual items are (re)parented to the rootQQuickItemso they get painted correctly. - After the object tree's loaded and the objects are instantiated you get the
completedsignal emitted and propagated through your items (visual and non-visual), so you can do w/e. - In the above code you attach to the
completedsignal, then you call thegetDatato retrieve the list of structs, thenforEachof 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 thetemplatecomponent (which isn't a visual item, as mentioned) and call it'screateObjectto create theTextitem contained. The first argument of the method is the visual parent (which is theColumnin 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; } -
@LeLev that works! Thanks for your patience on this.
May I ask why my approach didn't work?
@mzimmers
here is an example to illustrate, uncomment one or the other Component.onCompleted, you will observe that when we call
col.myArr.push("N° " + i) directly, the view is empty at the beginning but myArr actually contains the itemsWindow { width: 640 height: 480 visible: true Column{ id:col property var myArr : [] // calling myArr.push() directly in the function. // You will need to somehow "refresh" the view, (in this example see the button that resets the repeater model) // Component.onCompleted: { // var arr = [] // for(var i=0;i<10;i++){ // col.myArr.push("N° " + i) // } // } //OR ------------------------------------------ // creating a local array // Component.onCompleted: { // var arr = [] // for(var i=0;i<10;i++){ // arr.push("N° " + i) // } // col.myArr = arr; // } //-------------------------------------------------- Repeater{ id:rep model: col.myArr.length Text { id: txt text: col.myArr[index] } } //-------------------------------------------------- Button{ text: 'refresh' onClicked:{ rep.model = 0; rep.model = col.myArr.length; console.log(col.myArr) } // just to reset the repeater } } }It's great that your solution worked but why not implement something where you can simply write
Text { text: DataSource.getDataByIndex(0).name } -
@mzimmers
here is an example to illustrate, uncomment one or the other Component.onCompleted, you will observe that when we call
col.myArr.push("N° " + i) directly, the view is empty at the beginning but myArr actually contains the itemsWindow { width: 640 height: 480 visible: true Column{ id:col property var myArr : [] // calling myArr.push() directly in the function. // You will need to somehow "refresh" the view, (in this example see the button that resets the repeater model) // Component.onCompleted: { // var arr = [] // for(var i=0;i<10;i++){ // col.myArr.push("N° " + i) // } // } //OR ------------------------------------------ // creating a local array // Component.onCompleted: { // var arr = [] // for(var i=0;i<10;i++){ // arr.push("N° " + i) // } // col.myArr = arr; // } //-------------------------------------------------- Repeater{ id:rep model: col.myArr.length Text { id: txt text: col.myArr[index] } } //-------------------------------------------------- Button{ text: 'refresh' onClicked:{ rep.model = 0; rep.model = col.myArr.length; console.log(col.myArr) } // just to reset the repeater } } }It's great that your solution worked but why not implement something where you can simply write
Text { text: DataSource.getDataByIndex(0).name }@LeLev I may have spoken a bit too soon...when I try to apply this from the example to my application, I get the error again.
Column { id: myColumn property var myArray: [] Component.onCompleted: { var arr = [] BottleList.getBottleListQv().forEach(function(bottle) { arr.push(bottle.name) }, this); myColumn.myArray = arr; } Bottle { cellText: myColumn.myArray[0]//"W7" }The array element notation seems like it's correct; can you see what I'm doing wrong?
Regarding writing a function for the name: I would do that, and I still may, but I want to grow this to return several values in the struct: name, position, capacity, fill level, etc. So far, this approach looks better for that.
-
@LeLev I may have spoken a bit too soon...when I try to apply this from the example to my application, I get the error again.
Column { id: myColumn property var myArray: [] Component.onCompleted: { var arr = [] BottleList.getBottleListQv().forEach(function(bottle) { arr.push(bottle.name) }, this); myColumn.myArray = arr; } Bottle { cellText: myColumn.myArray[0]//"W7" }The array element notation seems like it's correct; can you see what I'm doing wrong?
Regarding writing a function for the name: I would do that, and I still may, but I want to grow this to return several values in the struct: name, position, capacity, fill level, etc. So far, this approach looks better for that.
-
@LeLev I may have spoken a bit too soon...when I try to apply this from the example to my application, I get the error again.
Column { id: myColumn property var myArray: [] Component.onCompleted: { var arr = [] BottleList.getBottleListQv().forEach(function(bottle) { arr.push(bottle.name) }, this); myColumn.myArray = arr; } Bottle { cellText: myColumn.myArray[0]//"W7" }The array element notation seems like it's correct; can you see what I'm doing wrong?
Regarding writing a function for the name: I would do that, and I still may, but I want to grow this to return several values in the struct: name, position, capacity, fill level, etc. So far, this approach looks better for that.
@mzimmers said in accessing aggregates (QVector of a struct):
@LeLev I may have spoken a bit too soon...when I try to apply this from the example to my application, I get the error again.
At the time you reference the array it's empty. That's the reason.
Component.onCompletedis executed after the property toBottle.cellTextis bound, so the JS engine throws you an error, there's no such thing asmyArray[0]at this point; the array is empty. -
@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...
@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.
You can instantiate them at runtime (as I did with the text item), you just need to replace the
Textwith your template for theBottle. Alternatively you can use theRepeater@LeLev's been pushing, but then set thedelegateproperty to a component which is going to create your bottle items (the repeater with a set delegate pretty much does what my example does). Note I don't know if themodelproperty is going to work with a list of gadgets, you need to check.@LeLev said in accessing aggregates (QVector of a struct):
it does almost exactly the same thing as my very first example/answer.
Almost but with fewer objects created under the hood. Not that it really matters as the QML engine is an elephant anyway.
-
@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.
You can instantiate them at runtime (as I did with the text item), you just need to replace the
Textwith your template for theBottle. Alternatively you can use theRepeater@LeLev's been pushing, but then set thedelegateproperty to a component which is going to create your bottle items (the repeater with a set delegate pretty much does what my example does). Note I don't know if themodelproperty is going to work with a list of gadgets, you need to check.@LeLev said in accessing aggregates (QVector of a struct):
it does almost exactly the same thing as my very first example/answer.
Almost but with fewer objects created under the hood. Not that it really matters as the QML engine is an elephant anyway.
@kshegunov said in accessing aggregates (QVector of a struct):
Alternatively you can use the Repeater @LeLev's been pushing
I just use a Repeater to answer to this question :
@mzimmers said in accessing aggregates (QVector of a struct):
@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]
}
}just shorter to write with a repeater
i'm really not pushing mzimmers to use it. it was just an example to show what is happening with "property var myArray: []" initialisation -
@mzimmers said in accessing aggregates (QVector of a struct):
@LeLev I may have spoken a bit too soon...when I try to apply this from the example to my application, I get the error again.
At the time you reference the array it's empty. That's the reason.
Component.onCompletedis executed after the property toBottle.cellTextis bound, so the JS engine throws you an error, there's no such thing asmyArray[0]at this point; the array is empty.@kshegunov said in accessing aggregates (QVector of a struct):
@mzimmers said in accessing aggregates (QVector of a struct):
@LeLev I may have spoken a bit too soon...when I try to apply this from the example to my application, I get the error again.
At the time you reference the array it's empty. That's the reason.
Component.onCompletedis executed after the property toBottle.cellTextis bound, so the JS engine throws you an error, there's no such thing asmyArray[0]at this point; the array is empty.That makes sense. So, according to the docs, I can use the onCompleted() handler with any object. Can I somehow use it with my Bottle (defined in QML) object?
-
@kshegunov said in accessing aggregates (QVector of a struct):
Alternatively you can use the Repeater @LeLev's been pushing
I just use a Repeater to answer to this question :
@mzimmers said in accessing aggregates (QVector of a struct):
@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]
}
}just shorter to write with a repeater
i'm really not pushing mzimmers to use it. it was just an example to show what is happening with "property var myArray: []" initialisation -
@kshegunov said in accessing aggregates (QVector of a struct):
Alternatively you can use the Repeater @LeLev's been pushing
I just use a Repeater to answer to this question :
@mzimmers said in accessing aggregates (QVector of a struct):
@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]
}
}just shorter to write with a repeater
i'm really not pushing mzimmers to use it. it was just an example to show what is happening with "property var myArray: []" initialisation@LeLev said in accessing aggregates (QVector of a struct):
i'm really not pushing mzimmers to use it. it was just an example to show what is happening with "property var myArray: []" initialisation
That came out badly. I didn't mean to imply it's wrong in any way, or that your answer is bad. A poor choice of words, sorry.
@mzimmers said in accessing aggregates (QVector of a struct):
That makes sense. So, according to the docs, I can use the onCompleted() handler with any object. Can I somehow use it with my Bottle (defined in QML) object?
Yes all the items can attach to it. Think of it as a "global" signal (sort of).
-
@LeLev said in accessing aggregates (QVector of a struct):
i'm really not pushing mzimmers to use it. it was just an example to show what is happening with "property var myArray: []" initialisation
That came out badly. I didn't mean to imply it's wrong in any way, or that your answer is bad. A poor choice of words, sorry.
@mzimmers said in accessing aggregates (QVector of a struct):
That makes sense. So, according to the docs, I can use the onCompleted() handler with any object. Can I somehow use it with my Bottle (defined in QML) object?
Yes all the items can attach to it. Think of it as a "global" signal (sort of).
@kshegunov no worries , i just wanted to clarify
@mzimmers said in accessing aggregates (QVector of a struct):
Can I somehow use it with my Bottle (defined in QML) object?
you can refactor the code in the Component.onCompleted into a function and call it when you need
-
@kshegunov no worries , i just wanted to clarify
@mzimmers said in accessing aggregates (QVector of a struct):
Can I somehow use it with my Bottle (defined in QML) object?
you can refactor the code in the Component.onCompleted into a function and call it when you need
@LeLev somehow, I'm overcomplicating this. I already have a function:
Q_INVOKABLE QString getBottleName(int index) { return m_bottleList[index].m_name; }The obstacle here is, anything I do is getting called at program startup, before my data structure is populated (as ksheg indicated). I can't figure out how to "delay" my assignment of the cellText property of the bottle until after that vector/list is built.