QObject as Q_PROPERTY best practices
-
Since you cannot copy QObjects and the only way I understand to represent a c++ structure in QML is by creating a object that derives from QObject and then defines Q_PROPERTY for each data member on that object. What is accepted as best practice when you have a nested struct of QObjects.
For example:
class CustomStruct: public QObject { Q_OBJECT Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged) public: explicit CustomStuct(QObject* parent = nullptr): QObject(parent){} ~CustomStruct() = default; int id() const { return m_id;} void setId(int new_value) { if(new_value != m_id) { m_id = new_value; emit idChanged(); } } signals: void idChanged(); private: int m_id; }; // then we have a class that consumes our custom struct class DataModel: public QObject { Q_OBJECT Q_PROPERTY(CustomStruct* custom_struct READ custom_struct WRITE setCustom_struct NOTIFY custom_structChanged) public: explicit DataModel(QObject* parent = nullptr): QObject(parent) { m_custom_struct = new CustomStruct(this); m_custom_struct->setID(5); } ~DataModel() = default; CustomStruct* custom_struct() const { // inst this bad practice.. cause caller could then delete the ptr? return m_custom_struct } void setCustom_struct(CustomStruct* new_value) { // what to do here do // do I key a change of ptr address (new_value != m_custom_struct).. then just update ptr ref // do I write a custom == operators and != operator in custom struct then copy the // actual data from new_value into m_custom_struct } signals: void custom_structChanged(); private: CustomStruct* m_custom_struct; };
Should you be able to set QObject* in Q_PROPERTY or should they be read and notify?
If you should be able set the QObject* in the Q_PROPERTY what is the best practice in setting it?
Change Ptr Ref and Notify?
Copy member values into current ptr ref and notify?
or something else?Any guidance would be much appreciated.
-
Why do you need a QObject structure in the first place?
Isn't Q_GADGET enough? -
@pjorourke said in QObject as Q_PROPERTY best practices:
Since you cannot copy QObjects
Yes, nor you should as they are not values.
and the only way I understand to represent a c++ structure in QML is by creating a object that derives from QObject and then defines Q_PROPERTY for each data member on that object.
Well, it isn't but that's beside the point. Tell us what you're trying to do exactly.
-
@kshegunov said in QObject as Q_PROPERTY best practices:
Well, it isn't but that's beside the point. Tell us what you're trying to do exactly.
Q_GADGET doesnt allow for notify signals. So say I did something like this
Label { text: dataModel.customStruct.id }
to my knowledge the label would never get any updates if :id" changed because its not binded to the Q_PROPERTY for customStruct but the Q_PROPERTY on the Q_GADGET for id. Thus I started to use QObjects instead of Q_GADGETS because I could not update the id property when emitting the signal for customStructChanged. Since its a QObject the binding on the QML would work because it would tie itself to the idChanged signal.
-
See comment above. To reiterate, how do i bind to a Q_GADGET on the QML such that when a member on the Q_GADGET changes all of its bindings in the QML are updated. Using the example in the original post how do I bind to the "id" memeber on a Q_GADGET. How do i tell the QML hey refresh your data bindings because the customstruct changed signal was fired which then would call the read id() on the Q_GADGET.
-
This the problem I run into using Q_GADGETS. Is when I nest them like so
#include <QObject> class CustomStruct2 { Q_GADGET Q_PROPERTY(QString id READ id WRITE setId) public: CustomStruct2() = default; ~CustomStruct2() = default; CustomStruct2(const CustomStruct2 &) = default; CustomStruct2 &operator=(const CustomStruct2 &) = default; CustomStruct2(CustomStruct2 &&) = default; CustomStruct2 &operator=(CustomStruct2 &&) = default; QString id(); void setId(QString id); private: QString m_id; }; class CustomStruct { Q_GADGET Q_PROPERTY(int id READ id WRITE setId) Q_PROPERTY(CustomStruct2 nested_struct READ nested_struct WRITE setNested_struct) public: CustomStruct() = default; ~CustomStruct() = default; CustomStruct(const CustomStruct &) = default; CustomStruct &operator=(const CustomStruct &) = default; CustomStruct(CustomStruct &&) = default; CustomStruct &operator=(CustomStruct &&) = default; int id(); void setId(int id); CustomStruct2 nested_struct() const; void setNested_struct(CustomStruct2 new_value); private: int m_id; CustomStruct2 m_nested_struct; }; Q_DECLARE_METATYPE(CustomStruct) Q_DECLARE_METATYPE(CustomStruct2)
Then using that struct in a QObject like so
#include <QObject> #include "gadget_def.h" class datamodel : public QObject { Q_OBJECT Q_PROPERTY(CustomStruct custom_struct READ custom_struct WRITE setCustom_struct NOTIFY custom_structChanged) public: explicit datamodel(QObject *parent = nullptr): QObject(parent) { } ~datamodel() = default; CustomStruct custom_struct() const { return m_custom_struct; } void setCustom_struct(CustomStruct new_value) { m_custom_struct = new_value; emit custom_structChanged(); } Q_INVOKABLE void refresh() { emit custom_structChanged(); } signals: void custom_structChanged(); private: CustomStruct m_custom_struct; };
Now calling in QML the nested_struct will never get updated
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 Window { width: 640 height: 480 visible: true title: qsTr("Hello World") RowLayout { width: parent.width height: parent.height Text { id: customStructID text: datamodel_cpp.custom_struct.id } Text { id: nestedStructID text: datamodel_cpp.custom_struct.nested_struct.id } Button { id:btnUpdateCustomStructID text: "Update Custom Struct" onClicked:{ datamodel_cpp.custom_struct.id = 60; } } Button { id:btnUpdateNestedStructID text: "Update Nested Struct" onClicked:{ // this wone force the customSturctChanged() signal to fire. datamodel_cpp.custom_struct.nested_struct.id = "updated!" } } } }
Setting up my main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <datamodel.h> #include <QQmlContext> int main(int argc, char *argv[]) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif QGuiApplication app(argc, argv); QQmlApplicationEngine engine; datamodel* dm = new datamodel(QCoreApplication::instance()); CustomStruct s = dm->custom_struct(); s.setId(5); CustomStruct2 s2; s2.setId(QString("Blue")); s.setNested_struct(s2); dm->setCustom_struct(s); engine.rootContext()->setContextProperty("datamodel_cpp", dm); 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(); }
-
I can't say for sure for nested gadgets, but this works correctly for me:
Item { property var person: Person { name: qsTr("Name") age { years: 1 months: 0 } Simulation.onAdvanced: { age.months++ } } ColumnLayout { Text { text: person.age.months } ToolButton { text: "Update age:" onClicked: person.age.months = 7 } } }
Where
Person
is aQObject
subclass with a couple of properties, one of which isage
, which is a gadget. This is with Qt 6 though, but I believe in Qt 5 the behavior should be the same. As far as I remember the docs, without actually checking, writing to the property of the gadget copies and writes the whole gadget back to the owning object. -
I have never seen a Q_GADGET created in QML like that. If you have a good example to point me to I would love to see it. I think I found out how update the nest gadgets in the qml. I needed to do this
var nested_struct = datamodel_cpp.custom_struct.nested_struct; nested_struct.id = "updated"; var volume_struct = nested_struct.volume_struct; volume_struct.volume = 33.2; nested_struct.volume_struct = volume_struct; datamodel_cpp.custom_struct.nested_struct = nested_struct;
By getting each levels gadget and setting it then once i got to the top level gadget it set everything correctly.
-
@pjorourke said in QObject as Q_PROPERTY best practices:
I have never seen a Q_GADGET created in QML like that. If you have a good example to point me to I would love to see it.
I don't follow. Do you mean the grouped properties syntax? This:
age { years: 1 months: 0 }
By getting each levels gadget and setting it then once i got to the top level gadget it set everything correctly.
Yes, that should work as well, but why do you have so many nested gadget instances? That's one of the reasons I asked what you're trying to do, I didn't mean in the technical sense.
-
@kshegunov
The data structure I have is 3 levels deep. Something like thisLevel1.Level2[i].Level3[j].property
When trying to develop a Qt object to mimic this structure is seemed over kill to have NOTIFY signals for each property at each level. It seemed like it would be easier to pass around the object as whole and just NOTIFY when the entire object changed. Mainly because I didn't see a way to update the entire NestedStructure when subclassed from QObject*. It just seems like a memory management and connection nightmare when ever I would say setNestedStruct(NestedStruct* new_value) . So I have been looking into Q_GADGET because its seemed more fit for the job because I could pass by value instead of reference. I also want to pass the NestedStructure object between objects on my backend (cpp) and cloning/copying the data between two NestedStructure* when subclassed from QObject just seemed like a bad practice. I could create a function that would just copy the Q_PROPERTYS if I were to subclass from QObject but again it just seemed like wrong way to do it. It seemed like correct way was to use Q_GADGET but I could never figure out how to get my QML to update correctly. Which with your examples above have shown me how. The only thing I dont like is that I cannot create Q_GADGET objects in QML I wish I could do that but maybe that support will come in the future.
-
@pjorourke said in QObject as Q_PROPERTY best practices:
The data structure I have is 3 levels deep. Something like this
Well, since you asked for advice, here it goes.
If you have hierarchical data, I'd go withQAbstractItemModel
. This whole concept of exposing arrays into QML isn't that great in practice. It's not wrong per se, but doesn't work as well. Otherwise you could of course do what you originally intended - keep things intoQObject
s, however then keep the object pointers intoQPointer
as you can loose the reference at any time due to QML garbage collection. QML is quiteQObject
heavy to begin with, but that isn't really ideal to represent "data" as such.The only thing I dont like is that I cannot create Q_GADGET objects in QML I wish I could do that but maybe that support will come in the future.
https://codereview.qt-project.org/c/qt/qtdeclarative/+/389027
and
https://codereview.qt-project.org/c/qt/qtdeclarative/+/389016
are relevant.