Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QObject as Q_PROPERTY best practices
Forum Updated to NodeBB v4.3 + New Features

QObject as Q_PROPERTY best practices

Scheduled Pinned Locked Moved Unsolved General and Desktop
11 Posts 3 Posters 2.2k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Christian EhrlicherC Offline
    Christian EhrlicherC Offline
    Christian Ehrlicher
    Lifetime Qt Champion
    wrote on last edited by
    #2

    Why do you need a QObject structure in the first place?
    Isn't Q_GADGET enough?

    Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
    Visit the Qt Academy at https://academy.qt.io/catalog

    P 1 Reply Last reply
    0
    • P pjorourke

      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.

      kshegunovK Offline
      kshegunovK Offline
      kshegunov
      Moderators
      wrote on last edited by kshegunov
      #3

      @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.

      Read and abide by the Qt Code of Conduct

      1 Reply Last reply
      0
      • P Offline
        P Offline
        pjorourke
        wrote on last edited by
        #4

        @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.

        1 Reply Last reply
        0
        • Christian EhrlicherC Christian Ehrlicher

          Why do you need a QObject structure in the first place?
          Isn't Q_GADGET enough?

          P Offline
          P Offline
          pjorourke
          wrote on last edited by pjorourke
          #5

          @Christian-Ehrlicher

          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.

          1 Reply Last reply
          0
          • P Offline
            P Offline
            pjorourke
            wrote on last edited by
            #6

            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();
            }
            
            kshegunovK 1 Reply Last reply
            0
            • P pjorourke

              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();
              }
              
              kshegunovK Offline
              kshegunovK Offline
              kshegunov
              Moderators
              wrote on last edited by
              #7

              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 a QObject subclass with a couple of properties, one of which is age, 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.

              Read and abide by the Qt Code of Conduct

              1 Reply Last reply
              0
              • P Offline
                P Offline
                pjorourke
                wrote on last edited by
                #8

                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.

                kshegunovK 1 Reply Last reply
                0
                • P pjorourke

                  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.

                  kshegunovK Offline
                  kshegunovK Offline
                  kshegunov
                  Moderators
                  wrote on last edited by kshegunov
                  #9

                  @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.

                  Read and abide by the Qt Code of Conduct

                  1 Reply Last reply
                  0
                  • P Offline
                    P Offline
                    pjorourke
                    wrote on last edited by pjorourke
                    #10

                    @kshegunov
                    The data structure I have is 3 levels deep. Something like this

                     Level1.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.

                    kshegunovK 1 Reply Last reply
                    0
                    • P pjorourke

                      @kshegunov
                      The data structure I have is 3 levels deep. Something like this

                       Level1.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.

                      kshegunovK Offline
                      kshegunovK Offline
                      kshegunov
                      Moderators
                      wrote on last edited by
                      #11

                      @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 with QAbstractItemModel. 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 into QObjects, however then keep the object pointers into QPointer as you can loose the reference at any time due to QML garbage collection. QML is quite QObject 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.

                      Read and abide by the Qt Code of Conduct

                      1 Reply Last reply
                      0

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved