accessing aggregates (QVector of a struct)
-
Thanks for the information, guys. I'm making progress, but still can't quite connect all the dots. I've created a BottleList class, with a method:
QVariantList BottleList::getBottleListQv() { QVariantList qvl; BottleData bd; for (int i = 0; i < NBR_BOTTLES; ++i) { bd = getBottleData(i); qvl.append(QVariant::fromValue(bd)); } return qvl; }
Another class ChangeConsumables creates an instance of BottleList.
from my QML file:
function getRackData() { var bottleRack = changeConsumablesViewModel.getBottleListQv() return bottleRack } Bottle { id: bottle1 cellText: "W7" cellColor: "red" }
I have 16 entries similar to bottle1.
So, what I'm missing is...how to replace the hard-coded "W7" with the name field from the BottleData struct in my BottleList class?
Thanks.
-
Thanks for the information, guys. I'm making progress, but still can't quite connect all the dots. I've created a BottleList class, with a method:
QVariantList BottleList::getBottleListQv() { QVariantList qvl; BottleData bd; for (int i = 0; i < NBR_BOTTLES; ++i) { bd = getBottleData(i); qvl.append(QVariant::fromValue(bd)); } return qvl; }
Another class ChangeConsumables creates an instance of BottleList.
from my QML file:
function getRackData() { var bottleRack = changeConsumablesViewModel.getBottleListQv() return bottleRack } Bottle { id: bottle1 cellText: "W7" cellColor: "red" }
I have 16 entries similar to bottle1.
So, what I'm missing is...how to replace the hard-coded "W7" with the name field from the BottleData struct in my BottleList class?
Thanks.
You can refer to the object by id. Like this:
function loadRackDataFirst() { bottle1.cellText = changeConsumablesViewModel.getBottleListQv()[0].name }
However you should convert to
QString
. -
Repeater { model: changeConsumablesViewModel.getBottleListQv() // or use property, a property will have a signal to update if the list changes Bottle { cellText: modelData.name } }
If you want more interaction then a model might be more appropriate.
modelData Search for modelData on that page to understand where it comes from.
-
You can refer to the object by id. Like this:
function loadRackDataFirst() { bottle1.cellText = changeConsumablesViewModel.getBottleListQv()[0].name }
However you should convert to
QString
.@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?
-
Repeater { model: changeConsumablesViewModel.getBottleListQv() // or use property, a property will have a signal to update if the list changes Bottle { cellText: modelData.name } }
If you want more interaction then a model might be more appropriate.
modelData Search for modelData on that page to understand where it comes from.
@fcarney that looks really powerful. The complete definition of each model is like this:
Bottle { id: bottle1 cellX: 25 cellY: 105 cellHeight: 75 cellWidth: 75 bottleScaleFactor: scaleFactor cellText: "W7" cellColor: "red" }
If I use the repeater to load the names, can I alter the individual bottles afterwards?
Thanks...
PS: I'm aware that there's a lot of ugly hard-coding in here; I was going to address that after I got the connections working.
-
If I use the repeater to load the names, can I alter the individual bottles afterwards?
It only alters the copy given to the Repeater. It has no way to get that data back.
I have not used Q_GADGETs before. What does it print out when you console.log(modelData)? Curious as what QML thinks that objects is.
-
@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):
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?
I "think" so. DOH! I meant to agree to the MEMBER macro doing that, not you misinterpreting this.
-
If I use the repeater to load the names, can I alter the individual bottles afterwards?
It only alters the copy given to the Repeater. It has no way to get that data back.
I have not used Q_GADGETs before. What does it print out when you console.log(modelData)? Curious as what QML thinks that objects is.
@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.)
-
@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) }