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.
  • P Offline
    P Offline
    pjorourke
    wrote on last edited by pjorourke
    #1

    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 1 Reply Last reply
    0
    • Christian EhrlicherC Online
      Christian EhrlicherC Online
      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