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_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; }
-
@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.onCompleted
is executed after the property toBottle.cellText
is 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
Text
with your template for theBottle
. Alternatively you can use theRepeater
@LeLev's been pushing, but then set thedelegate
property 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 themodel
property 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
Text
with your template for theBottle
. Alternatively you can use theRepeater
@LeLev's been pushing, but then set thedelegate
property 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 themodel
property 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.onCompleted
is executed after the property toBottle.cellText
is 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.onCompleted
is executed after the property toBottle.cellText
is 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).