Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. QML Singleton Exposes Empty QVariantList for Custom QVector Data

QML Singleton Exposes Empty QVariantList for Custom QVector Data

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
6 Posts 4 Posters 214 Views
  • 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
    Pivit
    wrote on last edited by
    #1

    I'm working on a modular Qt project using CMake where shared logic lives in a separate module. One of these modules defines a QML singleton (here called MyManager) that internally stores a QVector of a custom type (for example, a Light structure) and exposes this data via a Q_PROPERTY of type QVariantList. In the getter and setter, the QVector is converted to/from a QVariantList.

    The custom type is defined as follows (simplified for brevity):

    struct Light {
        uint id;
        quint16 intensity;
        QColor color;
        quint8 gobo;
        quint8 strobe;
        quint16 pan;
        quint16 tilt;
    
        bool operator==(const Light &other) const {
            return id == other.id &&
                   intensity == other.intensity &&
                   color == other.color &&
                   gobo == other.gobo &&
                   strobe == other.strobe &&
                   pan == other.pan &&
                   tilt == other.tilt;
        }
    };
    

    The singleton MyManager is declared like this:
    MyManager.h

    #ifndef MYMANAGER_H
    #define MYMANAGER_H
    
    #include <QObject>
    #include <QVariantList>
    #include <QQmlEngine>
    #include "Light.h"
    
    class MyManager : public QObject
    {
        Q_OBJECT
        QML_ELEMENT
        QML_SINGLETON
        Q_PROPERTY(QVariantList lightsList READ lightsList WRITE setLightsList NOTIFY lightsListChanged)
    public:
        explicit MyManager(QObject *parent = nullptr);
    
        QVariantList lightsList() const;
        void setLightsList(const QVariantList &list);
    
        Q_INVOKABLE void fillTestData();
    
    signals:
        void lightsListChanged();
    
    private:
        QVector<Light> m_lightsList;
    };
    
    #endif // MYMANAGER_H
    

    And in MyManager.cpp, I use helper templates to convert between QVector and QVariantList:

    #include "MyManager.h"
    #include <QDebug>
    
    template<typename T>
    QVariantList toVariantList(const QVector<T>& vector) {
        QVariantList list;
        for (const auto &item : vector) {
            list.append(QVariant::fromValue(item));
        }
        return list;
    }
    
    template<typename T>
    QVector<T> fromVariantList(const QVariantList &list) {
        QVector<T> vector;
        for (const auto &item : list) {
            vector.append(item.value<T>());
        }
        return vector;
    }
    
    MyManager::MyManager(QObject *parent) : QObject(parent) {}
    
    QVariantList MyManager::lightsList() const {
        return toVariantList(m_lightsList);
    }
    
    void MyManager::setLightsList(const QVariantList &list) {
        auto converted = fromVariantList<Light>(list);
        if (m_lightsList == converted)
            return;
        m_lightsList = converted;
        emit lightsListChanged();
    }
    
    void MyManager::fillTestData() {
        m_lightsList.clear();
        for (int i = 0; i < 5; ++i) {
            m_lightsList.append({
                static_cast<uint>(i),
                static_cast<quint16>(i * 100),
                QColor(255, 0, 0),
                1, 2, 300, 400
            });
        }
        qDebug() << "Filled lights:" << m_lightsList.count();
        emit lightsListChanged();
    }
    

    In my main application, I register the singleton after calling fillTestData():

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include "MyManager.h"
    
    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
        QQmlApplicationEngine engine;
    
        MyManager manager;
        manager.fillTestData();  // Debug output confirms that data is filled
    
        // Register as a QML singleton under the module "MyModule"
        qmlRegisterSingletonInstance("MyModule", 1, 0, "MyManager", &manager);
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        return app.exec();
    }
    

    Finally, in QML I access the data like this:

    import QtQuick 2.15
    import MyModule 1.0
    
    Item {
        Component.onCompleted: {
             console.log("Lights List:", MyManager.lightsList)
        }
    }
    

    Even though the debug output confirms that the QVector is populated in fillTestData(), the lightsList property in QML is always empty. I’m not sure if this is due to the conversion method, the custom type registration, or some subtle issue with the singleton registration.

    Has anyone encountered similar issues exposing a QVector of a custom type via QVariantList in a QML singleton? Any suggestions on what might be wrong or how to correctly expose the data to QML would be greatly appreciated.

    Thanks in advance!

    1 Reply Last reply
    0
    • S Offline
      S Offline
      Shrishashank
      wrote on last edited by Shrishashank
      #2

      It looks like your issue might be due to the custom type Light not being registered properly for use in QVariant and QML. To fix this:

      Register the Light type: You need to tell Qt how to handle your Light type by registering it with the meta-object system. Add this line in your
      main.cpp (before using Light with QVariant or QML):

      qRegisterMetaType<Light>("Light");
      

      Ensure Light is QVariant-compatible: Make sure Light is properly handled by QVariant. By registering it as mentioned above, Qt will know how to store and retrieve Light in QVariant.
      Check QML code: In QML, make sure you're accessing the lightsList property correctly. Here's an example of how you might log each light in the list:

      Component.onCompleted: {
          console.log("Lights List:", MyManager.lightsList)
          MyManager.lightsList.forEach(light => {
              console.log("Light ID:", light.id)
          });
      }
      

      Debug the data: Double-check that the data is actually getting added to m_lightsList in C++ and that the lightsListChanged() signal is emitted when the data changes.
      By making sure Light is registered and QVariant-compatible, your lightsList should show up correctly in QML.

      SHRISHASHANK S K

      1 Reply Last reply
      0
      • P Offline
        P Offline
        Pivit
        wrote on last edited by
        #3

        Hey Shrishashank,

        Thanks for the quick reply.
        I registered the Light class in my main.cpp with the qRegisterMetaType but still got an empty list.

        After a bit more debugging I figured out that the object created in my main.cpp and on which I call fillTestData(), was not the same object as the one QML is trying to access (different memory addresses). After calling the function inside of qml on Component.onComplete like this:

        Component.onCompleted: {
                PatchManager.fillTestData();
                console.log("PatchManager:", PatchManager);
                console.log("Lights List:", PatchManager.lightsList)
                PatchManager.lightsList.forEach(light => {
                    console.log("Light ID:", light.id)
                });
            }
        

        I now do get elements inside of the lightsList but when trying to acces its properties like .id I get Undefined.

        Output:

        qml: PatchManager: PatchManager(0x16922a9e060)
        qml: Lights List: [QVariant(Light, ),QVariant(Light, ),QVariant(Light, ),QVariant(Light, ),QVariant(Light, )]
        qml: Light ID: undefined
        qml: Light ID: undefined
        qml: Light ID: undefined
        qml: Light ID: undefined
        qml: Light ID: undefined
        

        Do you have any idea why the object created in my main.cpp and in my qml file are not the same object?
        And why i cannot access the properties of the Light struct, even though Lights is using just regular Qt/C++ containers?

        Thanks in advance for helping me out!

        B 1 Reply Last reply
        0
        • P Pivit

          Hey Shrishashank,

          Thanks for the quick reply.
          I registered the Light class in my main.cpp with the qRegisterMetaType but still got an empty list.

          After a bit more debugging I figured out that the object created in my main.cpp and on which I call fillTestData(), was not the same object as the one QML is trying to access (different memory addresses). After calling the function inside of qml on Component.onComplete like this:

          Component.onCompleted: {
                  PatchManager.fillTestData();
                  console.log("PatchManager:", PatchManager);
                  console.log("Lights List:", PatchManager.lightsList)
                  PatchManager.lightsList.forEach(light => {
                      console.log("Light ID:", light.id)
                  });
              }
          

          I now do get elements inside of the lightsList but when trying to acces its properties like .id I get Undefined.

          Output:

          qml: PatchManager: PatchManager(0x16922a9e060)
          qml: Lights List: [QVariant(Light, ),QVariant(Light, ),QVariant(Light, ),QVariant(Light, ),QVariant(Light, )]
          qml: Light ID: undefined
          qml: Light ID: undefined
          qml: Light ID: undefined
          qml: Light ID: undefined
          qml: Light ID: undefined
          

          Do you have any idea why the object created in my main.cpp and in my qml file are not the same object?
          And why i cannot access the properties of the Light struct, even though Lights is using just regular Qt/C++ containers?

          Thanks in advance for helping me out!

          B Offline
          B Offline
          Bob64
          wrote on last edited by Bob64
          #4

          @Pivit I tried your example but for now I have only this in my onCompleted:

                  console.log("Lights List:", MyManager.lightsList)
                  console.log("Lights List length:", MyManager.lightsList.length)
          

          and if I debug from the QML into the C++, I see that the correct manager object is called and that the lisghtsList is non-empty:

          Filled lights: 5
          qml: Lights List: [QVariant(Light, ),QVariant(Light, ),QVariant(Light, ),QVariant(Light, ),QVariant(Light, )]
          qml: Lights List length: 5
          
          

          I think your issue is that you have not properly exposed Light to QML. For a simple struct-type class the easiest thing is to make it a Q_GADGET to expose the properties to QML:

          #include <QVariant>
          
          struct Light
          {
          private:
              Q_GADGET
          
              Q_PROPERTY(uint id MEMBER id)
              Q_PROPERTY(uint intensity MEMBER intensity)
          public:
              uint id;
              quint16 intensity;
              // ... same as your code
          };
          
          Q_DECLARE_METATYPE(Light)
          

          I have only exposed the id and intensity members for now, but it is enough to try it out. I added an extra print in the onCompleted:

          console.log("Lights List first id:", MyManager.lightsList[0].id)

          The output is now:

          Filled lights: 5
          qml: Lights List: [Light(0, 0),Light(1, 100),Light(2, 200),Light(3, 300),Light(4, 400)]
          qml: Lights List length: 5
          qml: Lights List first id: 0
          
          1 Reply Last reply
          0
          • P Offline
            P Offline
            Pivit
            wrote on last edited by
            #5

            After some further fiddling around, i've managed to print the correct Light Id's to the console in the onComplete function. Now when trying to actually access this data in a ListView, nothing is showing. It cannot even access how many items are in this list since just showing a white-bordered rectangle for each entry is not showing anything.

                        ListView {
                            id: listView
                            Layout.fillWidth: true
                            Layout.fillHeight: true
                            clip: true
                            spacing: 5
                            model: MyManager.lightsList
            
                            delegate: Rectangle {
                                width: listView.width
                                height: 60
                                color: "#1c1c1c"
                                radius: 5
                                border.color: "#ffffff"
                                border.width: 2
                                anchors.margins: 5
            
                                Text {
                                    text: modelData.id
                                    anchors.left: parent.left
                                    anchors.leftMargin: 10
                                    anchors.verticalCenter: parent.verticalCenter
                                    color: "white"
                                    font.pixelSize: 16
                                }
                            }
            

            I've figured out that defining the Light struct as a Q_GADGET is not enough for showing it in a ListView since there are no signal/slots available in Q_GADGET.

            So i've changed the whole Light struct into a QObject:

            class Light : public QObject
            {
                Q_OBJECT
                Q_PROPERTY(uint id READ id WRITE setId NOTIFY idChanged)
                Q_PROPERTY(quint16 intensity READ intensity WRITE setIntensity NOTIFY intensityChanged)
                Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
                Q_PROPERTY(quint8 gobo READ gobo WRITE setGobo NOTIFY goboChanged)
                Q_PROPERTY(quint8 strobe READ strobe WRITE setStrobe NOTIFY strobeChanged)
                Q_PROPERTY(quint16 pan READ pan WRITE setPan NOTIFY panChanged)
                Q_PROPERTY(quint16 tilt READ tilt WRITE setTilt NOTIFY tiltChanged)
            public:
                explicit Light(QObject* parent = nullptr)
                    : QObject(parent),
                    m_id(0),
                    m_intensity(0),
                    m_color(Qt::black),
                    m_gobo(0),
                    m_strobe(0),
                    m_pan(0),
                    m_tilt(0)
                {}
            
                uint id() const { return m_id; }
                void setId(uint id) {
                    if (m_id != id) {
                        m_id = id;
                        emit idChanged();
                    }
                }
            
                quint16 intensity() const { return m_intensity; }
                void setIntensity(quint16 intensity) {
                    if (m_intensity != intensity) {
                        m_intensity = intensity;
                        emit intensityChanged();
                    }
                }
            
                QColor color() const { return m_color; }
                void setColor(const QColor &color) {
                    if (m_color != color) {
                        m_color = color;
                        emit colorChanged();
                    }
                }
            
                quint8 gobo() const { return m_gobo; }
                void setGobo(quint8 gobo) {
                    if (m_gobo != gobo) {
                        m_gobo = gobo;
                        emit goboChanged();
                    }
                }
            
                quint8 strobe() const { return m_strobe; }
                void setStrobe(quint8 strobe) {
                    if (m_strobe != strobe) {
                        m_strobe = strobe;
                        emit strobeChanged();
                    }
                }
            
                quint16 pan() const { return m_pan; }
                void setPan(quint16 pan) {
                    if (m_pan != pan) {
                        m_pan = pan;
                        emit panChanged();
                    }
                }
            
                quint16 tilt() const { return m_tilt; }
                void setTilt(quint16 tilt) {
                    if (m_tilt != tilt) {
                        m_tilt = tilt;
                        emit tiltChanged();
                    }
                }
            
                // Equality operator comparing each property
                bool operator==(const Light &other) const {
                    return m_id == other.m_id &&
                           m_intensity == other.m_intensity &&
                           m_color == other.m_color &&
                           m_gobo == other.m_gobo &&
                           m_strobe == other.m_strobe &&
                           m_pan == other.m_pan &&
                           m_tilt == other.m_tilt;
                }
            
            signals:
                void idChanged();
                void intensityChanged();
                void colorChanged();
                void goboChanged();
                void strobeChanged();
                void panChanged();
                void tiltChanged();
            
            private:
                uint m_id;
                quint16 m_intensity;
                QColor m_color;
                quint8 m_gobo;
                quint8 m_strobe;
                quint16 m_pan;
                quint16 m_tilt;
            };
            

            After this change it still prints out the correct stuff in the output log but would still not show anything in the ListView...

            qml: MyManager: MyManager(0x2cc521f0fa0)
            qml: Lights List: [Light(0x2cc5543c410),Light(0x2cc5543c6b0),Light(0x2cc5543c830),Light(0x2cc5543c590),Light(0x2cc5543cbf0)]
            qml: Light ID: 0
            qml: Light ID: 1
            qml: Light ID: 2
            qml: Light ID: 3
            qml: Light ID: 4
            

            Any thoughts?

            JoeCFDJ 1 Reply Last reply
            0
            • P Pivit

              After some further fiddling around, i've managed to print the correct Light Id's to the console in the onComplete function. Now when trying to actually access this data in a ListView, nothing is showing. It cannot even access how many items are in this list since just showing a white-bordered rectangle for each entry is not showing anything.

                          ListView {
                              id: listView
                              Layout.fillWidth: true
                              Layout.fillHeight: true
                              clip: true
                              spacing: 5
                              model: MyManager.lightsList
              
                              delegate: Rectangle {
                                  width: listView.width
                                  height: 60
                                  color: "#1c1c1c"
                                  radius: 5
                                  border.color: "#ffffff"
                                  border.width: 2
                                  anchors.margins: 5
              
                                  Text {
                                      text: modelData.id
                                      anchors.left: parent.left
                                      anchors.leftMargin: 10
                                      anchors.verticalCenter: parent.verticalCenter
                                      color: "white"
                                      font.pixelSize: 16
                                  }
                              }
              

              I've figured out that defining the Light struct as a Q_GADGET is not enough for showing it in a ListView since there are no signal/slots available in Q_GADGET.

              So i've changed the whole Light struct into a QObject:

              class Light : public QObject
              {
                  Q_OBJECT
                  Q_PROPERTY(uint id READ id WRITE setId NOTIFY idChanged)
                  Q_PROPERTY(quint16 intensity READ intensity WRITE setIntensity NOTIFY intensityChanged)
                  Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
                  Q_PROPERTY(quint8 gobo READ gobo WRITE setGobo NOTIFY goboChanged)
                  Q_PROPERTY(quint8 strobe READ strobe WRITE setStrobe NOTIFY strobeChanged)
                  Q_PROPERTY(quint16 pan READ pan WRITE setPan NOTIFY panChanged)
                  Q_PROPERTY(quint16 tilt READ tilt WRITE setTilt NOTIFY tiltChanged)
              public:
                  explicit Light(QObject* parent = nullptr)
                      : QObject(parent),
                      m_id(0),
                      m_intensity(0),
                      m_color(Qt::black),
                      m_gobo(0),
                      m_strobe(0),
                      m_pan(0),
                      m_tilt(0)
                  {}
              
                  uint id() const { return m_id; }
                  void setId(uint id) {
                      if (m_id != id) {
                          m_id = id;
                          emit idChanged();
                      }
                  }
              
                  quint16 intensity() const { return m_intensity; }
                  void setIntensity(quint16 intensity) {
                      if (m_intensity != intensity) {
                          m_intensity = intensity;
                          emit intensityChanged();
                      }
                  }
              
                  QColor color() const { return m_color; }
                  void setColor(const QColor &color) {
                      if (m_color != color) {
                          m_color = color;
                          emit colorChanged();
                      }
                  }
              
                  quint8 gobo() const { return m_gobo; }
                  void setGobo(quint8 gobo) {
                      if (m_gobo != gobo) {
                          m_gobo = gobo;
                          emit goboChanged();
                      }
                  }
              
                  quint8 strobe() const { return m_strobe; }
                  void setStrobe(quint8 strobe) {
                      if (m_strobe != strobe) {
                          m_strobe = strobe;
                          emit strobeChanged();
                      }
                  }
              
                  quint16 pan() const { return m_pan; }
                  void setPan(quint16 pan) {
                      if (m_pan != pan) {
                          m_pan = pan;
                          emit panChanged();
                      }
                  }
              
                  quint16 tilt() const { return m_tilt; }
                  void setTilt(quint16 tilt) {
                      if (m_tilt != tilt) {
                          m_tilt = tilt;
                          emit tiltChanged();
                      }
                  }
              
                  // Equality operator comparing each property
                  bool operator==(const Light &other) const {
                      return m_id == other.m_id &&
                             m_intensity == other.m_intensity &&
                             m_color == other.m_color &&
                             m_gobo == other.m_gobo &&
                             m_strobe == other.m_strobe &&
                             m_pan == other.m_pan &&
                             m_tilt == other.m_tilt;
                  }
              
              signals:
                  void idChanged();
                  void intensityChanged();
                  void colorChanged();
                  void goboChanged();
                  void strobeChanged();
                  void panChanged();
                  void tiltChanged();
              
              private:
                  uint m_id;
                  quint16 m_intensity;
                  QColor m_color;
                  quint8 m_gobo;
                  quint8 m_strobe;
                  quint16 m_pan;
                  quint16 m_tilt;
              };
              

              After this change it still prints out the correct stuff in the output log but would still not show anything in the ListView...

              qml: MyManager: MyManager(0x2cc521f0fa0)
              qml: Lights List: [Light(0x2cc5543c410),Light(0x2cc5543c6b0),Light(0x2cc5543c830),Light(0x2cc5543c590),Light(0x2cc5543cbf0)]
              qml: Light ID: 0
              qml: Light ID: 1
              qml: Light ID: 2
              qml: Light ID: 3
              qml: Light ID: 4
              

              Any thoughts?

              JoeCFDJ Offline
              JoeCFDJ Offline
              JoeCFD
              wrote on last edited by
              #6

              @Pivit QStringListModel c++ model or QML ListModel is needed for the model in listview. If QStringListModel is applied, define a C++ class to inherit QStringListModel and make it accessible to qml.

              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