Solved updating elements in a repeater?
-
@mzimmers The following is a trivial example:
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> struct Bottle { Q_GADGET Q_PROPERTY(quint32 volume MEMBER m_volume) Q_PROPERTY(quint32 amountNeeded MEMBER m_amountNeeded) Q_PROPERTY(int position MEMBER m_position) Q_PROPERTY(QString name MEMBER m_name) public: quint32 m_volume; // amount in bottle (in uL) quint32 m_amountNeeded; // amount needed for synth (in uL) int m_position; // still figuring this one out QString m_name; // name of the reagent }; typedef QVector<Bottle> Bottles; class BottleManager: public QObject{ Q_OBJECT Q_PROPERTY(QVariantList bottles READ bottles NOTIFY bottlesChanged) public: QVariantList bottles() const{ return m_bottles; } void append(const Bottle & bottle){ m_bottles << QVariant::fromValue(bottle); Q_EMIT bottlesChanged(); } void clear(){ m_bottles.clear(); Q_EMIT bottlesChanged(); } Q_SIGNALS: void bottlesChanged(); private: QVariantList m_bottles; }; int main(int argc, char *argv[]) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif BottleManager manager; manager.append({100, 10, 100, "item1"}); manager.append({10, 100, 10, "item2"}); manager.append({100, 10, 10, "item3"}); manager.append({100, 10, 10, "item4"}); manager.append({10, 100, 10, "item5"}); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("bottlesmanager", &manager); const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); } #include "main.moc"
main.qml
import QtQuick 2.12 import QtQuick.Window 2.12 Window { width: 640 height: 480 visible: true title: qsTr("Hello World") Column{ Repeater{ model: bottlesmanager.bottles Rectangle{ width: 100 height: 100 border.color: "black" color: model.modelData.volume < model.modelData.amountNeeded ? "green": "red" Text{ anchors.centerIn: parent text: model.modelData.name } } } } }
-
Thanks for the detailed example. I understand most of it, but...what is this for?
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection);
-
@mzimmers hmm, that's part of the current Qt template for those kinds of projects. Previously it was verified that there is at least one rootObject but that does not guarantee that it works, so now it is preferred to verify using the objectCreated signal
-
@eyllanesc ah...OK, that's being taken care of elsewhere in the project setting, so I don't need to deal with that. (I do set the context property.)
So, I guess the advantage of this approach is, no duplication of data?
Nit: your comparison of volume/amountNeeded is backwards.
-
@mzimmers said in updating elements in a repeater?:
@kshegunov your approach looks great, but I'm curious as to exactly what about it you prefer over the others.
Because I assume that at some later time you/I/whoever are/am/is going to want to tie it with a C++ backend. So, I'd rather not stick to QtQuick items, but either directly expose an array of
QObejct
s or define aQAbstractItemModel
and use that. It's not better, it's just that I've learned over the years that requirements have this peculiar property of changing themselves midway.@mzimmers said in updating elements in a repeater?:
Thanks for the detailed example. I understand most of it, but...what is this for?
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection);
This is what is emitted when a quick item is created through a component that's loaded from a file (either your main file, or with a
Loader
). It's a dummy as it just kills the application if there's an error, but you could possibly attach there to handle the failure if you wish and if you allow your UI to, say, be edited without recompiling the application.@eyllanesc ah...OK, that's being taken care of elsewhere in the project setting, so I don't need to deal with that. (I do set the context property.)
So, I guess the advantage of this approach is, no duplication of data?
You're now being naive. ;)
This:
Text{ anchors.centerIn: parent text: model.modelData.name }
copies the
QString
(a shallow copy). -
@kshegunov said in updating elements in a repeater?:
Because I assume that at some later time you/I/whoever are/am/is going to want to tie it with a C++ backend. So, I'd rather not stick to QtQuick items, but either directly expose an array of QObejcts or define a QAbstractItemModel and use that.
Agreed 100%. In this spirit, I'm trying to restructure my application like this:
- I have a struct Bottle, based on Q_GADGET. This contains information intrinsic to the bottle (size, dimensions, contents, etc.).
- I have a class ReagentManager that contains a private QVector of Bottles. An object of this class is registered as a context property to make it visible to the QML.
- One QML view contains a repeater for bottles, containing UI-specific information (size, screen location).
So...while my ReagentManager class is visible to the QML, the QVector of Bottles is not. I can write Q_INVOKABLE access routines for each of them, but I'm curious as to whether there might be a better way of doing it.
Thanks for any input.
My C++ code maintains an instance of the class ReagentManager, so I'm confident that its contents are always current. Now: how best to do something like this:
-
@mzimmers said in updating elements in a repeater?:
So...while my ReagentManager class is visible to the QML, the QVector of Bottles is not. I can write Q_INVOKABLE access routines for each of them, but I'm curious as to whether there might be a better way of doing it.
Make the bottles
QObject
instead of them beingQ_GADGET
and expose their properties (look up theQ_PROPERTY
docs and be sure to have the notification signals). After that the QML part remains pretty much the same, the change in theQObject
is going to be reflected naturally into the QML scene without anything more than you binding the properties on creation. -
@kshegunov thanks. I'm still a little UNclear on the binding details; what would be an example of a bind using your code above?
-
Bottle { cellX: modelData.x //< If modelData is QObject, this is a property binding cellY: modelData.y cellColor: modelData.color }
-
Trying this:
struct Bottle : public QObject { Q_OBJECT Q_PROPERTY(quint32 volume MEMBER m_volume NOTIFY volumeChanged) Q_PROPERTY(quint32 minVolume MEMBER m_minVolume NOTIFY minVolumeChanged) Q_PROPERTY(quint32 amountNeeded MEMBER m_amountNeeded NOTIFY amountNeededChanged) Q_PROPERTY(int slotNumber MEMBER m_slotNumber) Q_PROPERTY(QString name MEMBER m_name) Q_PROPERTY(ReagentBottleType bottleType MEMBER m_bottleType) public: // needed to represent amounts in int, not float // because using floats causes a float-equal error // in the generated MOC file. quint32 m_volume; // amount in bottle (in uL) quint32 m_minVolume; // amount in bottle that can't be used (in uL) quint32 m_amountNeeded; // amount needed for synth (in uL) int m_slotNumber; // still figuring this one out QString m_name; // name of the reagent ReagentBottleType m_bottleType; // bottle type. signals: void volumeChanged(); void minVolumeChanged(); void amountNeededChanged(); };
Getting this when I try to build:
/home/mzimmers/git/KOL-UI/src/lib/change_consumables/reagent_manager.h:11: error: use of deleted function ‘QObject::QObject(const QObject&)’ In file included from /home/mzimmers/git/KOL-UI/src/lib/change_consumables/reagent_manager.cpp:7:0: /home/mzimmers/git/KOL-UI/src/lib/change_consumables/reagent_manager.h:11:8: error: use of deleted function ‘QObject::QObject(const QObject&)’ struct Bottle : private QObject { ^~~~~~
Do I have to convert Bottle from a struct to a C++ class?
-
@mzimmers QObject is not copyable so you have to remove the copy constructor from Bottle.
-
@mzimmers said in updating elements in a repeater?:
Do I have to convert Bottle from a struct to a C++ class?
No, but you can't keep the objects in a
QVariantList
, becauseQObject
s can't be copied. You need to switch toQList<QObject *>
.PS.
Additional notes:- The notifier signals should report the new value of the property (look up the documentation examples).
- If you need to compare floats, then you should probably stick to defining your own setters and getters and registering them with
READ
/WRITE
in theQ_PROPERTY
definition instead of relying onMEMBER
.
-
@kshegunov said in updating elements in a repeater?:
No, but you can't keep the objects in a QVariantList, because QObjects can't be copied.
I must be going crazy, but...where in my struct am I using QVariantList?
Thanks for the note about including the new values in the signals.
I'm avoiding floats for the reason you cited, plus a couple more.
-
@mzimmers said in updating elements in a repeater?:
I must be going crazy, but...where in my struct am I using QVariantList?
Nowhere, but how do you marshal the objects to QML?
-
@kshegunov said in updating elements in a repeater?:
@mzimmers said in updating elements in a repeater?:
I must be going crazy, but...where in my struct am I using QVariantList?
Nowhere, but how do you marshal the objects to QML?
Well, like this (I think this is what you're asking):
Column { id: myColumn Repeater { id: bottleRepeater model: bottleModel Bottle { cellX: model.x cellY: model.y cellHeight: model.height cellWidth: model.width volume: model.volume bottleScaleFactor: scaleFactor }
But if I'm doing something wrong in QML, why is the compiler giving me an error pointed at my struct?
-
@mzimmers said in updating elements in a repeater?:
Well, like this (I think this is what you're asking):
I mean from the C++ side. What is
bottleModel
and where does it come from? -
From the same QML file:
ListModel { id: bottleModel ListElement { // position 1 x: 400 y: 17 height: 75 width: 75 } ...
-
This is confusing, I thought the data is supposed to come from C++.
-
I know, I'm doing a lousy job of explaining this. in C++:
typedef QVector<Bottle> Bottles; class ReagentManager : public QObject { Q_OBJECT private: Bottles m_bottleList; } <in another file> ReagentManager m_reagentManager; engine->rootContext()->setContextProperty("reagentManager", &m_reagentManager);
In QML:
onVisibleChanged: { if (visible) { reagentManager.updateBottleList() rack.updateBottles() } } // update our QML array based on the C++ model. function updateBottles() { var modelSize = bottleModel.count var i var l_color var volume var minVolume var amountNeeded var name for (i = 0; i < modelSize; ++i) { name = reagentManager.getName(i) bottleRepeater.itemAt(i).cellText = name volume = reagentManager.m_volume minVolume = reagentManager.getMinVolume(i) amountNeeded = reagentManager.getAmountNeeded(i) l_color = ((volume - minVolume) >= amountNeeded) ? "green" : "red" bottleRepeater.itemAt(i).cellColor = l_color } }
So, my QML function calls C++ routines to obtain the needed data. I'm trying to convert this to the approach you suggested; this is where I ran into the problem with the struct.
I still don't see where the QVariantList comes into play, though.
-
typedef QVector<Bottle> Bottles;
If
Bottle
is derived fromQObject
you can't keep it directly in a vector (can't copy the objects). You need to keepBottle *
there.