Best way to access a cpp structure in QML



  • I need to pass structures between cpp and QML. If i use property i should create an individual set and get functions, My structure contains minimum 5 members so i felt it's not good to use set and get for all those members.
    Following is an example of what i am trying to do :

    MyClass.h

    #include <QObject>
    #include <QDebug>
    using namespace std;
    
    struct MyStruct
    {
        int val;
        QString name1 ;
        QString name2 ;
        QString name3 ;
        QString name4 ;
    };
    
    class MyClass:public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QVariant myVariant READ getmyVariant
                    WRITE setmyVariant NOTIFY myVariantChanged)
    
    public:
        explicit MyClass(QObject *parent = nullptr);
        MyStruct strObj;
        
         QVariant getmyVariant() const
         {
             QVariant var;
             var.setValue(strObj);
             return var;
         }
    
    void setmyVariant(<Arguments>)
    {
        //How to set the value from QML
    }
    
    signals:
    void myVariantChanged();
    
    }
    

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include <QDebug>
    #include <QObject>
    
    #include "MyClass.h"
    
    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
        QQmlApplicationEngine engine;
    
        MyClass classObj;
    
        engine.rootContext()->setContextProperty("classObj",&classObj);
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    

    Main.qml

    import QtQuick 2.6
    import QtQuick.Controls 2.2
    import QtQuick.Window 2.3
    
    ApplicationWindow {
    
        id: applicationWindow
    
        visible: true
        width: 600
        height: 400
        title: qsTr("My App")
    
        MainForm{
            id : mainform
    
            Component.onCompleted: {
            console.log("name===="+classObj.myVariant.name1) //***undefined
            }
        }
    }
    

    If i print just (classObj.myVariant) i am getting QVariant(MyStruct) but when i tried to access any parameter like classObj.myVariant.name1 i am getting "undefined" and also how to set a variant from QML?



  • If your struct really is just 5 QStrings, personally I'd just use a QStringList (which I believe QVariant supports nicely) to communicate its state into (and from) the QML/Javascript domain.



  • Hi! Simple answer is: no. To make the members' names available to QML your object has to derive from QObject and expose the members via Q_PROPERTY. If, for some reason, you need to keep the struct as it is, you'll need to provide a wrapper class as an interface to QML, like:

    #ifndef MYWRAPPER_H
    #define MYWRAPPER_H
    
    #include <QObject>
    
    class MyStruct;
    
    class MyWrapper : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(int val READ val WRITE setVal NOTIFY valChanged)
        // ...
    
    public:
        explicit MyWrapper(QObject *parent = nullptr);
        ~MyWrapper();
    
        int val() const;
        void setVal(int v);
        // ...
    
    signals:
        valChanged(int);
        // ...
    
    private:
        MyStruct *m_myStruct;
    };
    
    #endif // MYWRAPPER_H
    

    A QVariant with a POD inside does not expose its members' names to the QML environment. If you want to go with QVariant and have names you need to use QVariantMap. Here is a class "Car" that exposes your MyStruct as a QVariantMap:

    car.h

    #ifndef CAR_H
    #define CAR_H
    
    #include <QObject>
    #include <QVariantMap>
    #include "mystruct.h"
    
    class Car : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QVariantMap myStruct READ getMyStruct WRITE setMyStruct NOTIFY myStructChanged)
    
    public:
        Car(QObject *parent = nullptr);
    
        QVariantMap getMyStruct() const;
        void setMyStruct(QVariantMap myStruct);
    
    signals:
        void myStructChanged(QVariantMap myStruct);
    
    private:
        MyStruct m_myStruct;
    };
    
    #endif // CAR_H
    

    car.cpp

    #include "car.h"
    #include "myadapter.h"
    
    Car::Car(QObject *parent)
        : QObject(parent)
    {
    }
    
    QVariantMap Car::getMyStruct() const
    {
        return myStructToQVariantMap(m_myStruct);
    }
    
    void Car::setMyStruct(QVariantMap myStruct)
    {
        MyStruct newValue = myStructFromQVariantMap(myStruct);
        if (myStructEqual(m_myStruct, newValue))
            return;
        m_myStruct = newValue;
        emit myStructChanged(myStruct);
    }
    

    It relies on some helper functions:
    myadapter.h

    #ifndef MYADAPTER_H
    #define MYADAPTER_H
    
    #include "mystruct.h"
    #include <QVariantMap>
    
    QVariantMap myStructToQVariantMap(MyStruct const &myStruct);
    MyStruct myStructFromQVariantMap(QVariantMap const &vm);
    bool myStructEqual(MyStruct const &myStruct1, MyStruct const &myStruct2);
    
    #endif // MYADAPTER_H
    

    myadapter.cpp

    #include "myadapter.h"
    
    QVariantMap myStructToQVariantMap(const MyStruct &myStruct)
    {
        QVariantMap res;
        res.insert("val", myStruct.val);
        res.insert("name1", myStruct.name1);
        res.insert("name2", myStruct.name2);
        res.insert("name3", myStruct.name3);
        res.insert("name4", myStruct.name4);
        return res;
    }
    
    MyStruct myStructFromQVariantMap(const QVariantMap &vm)
    {
        MyStruct res;
        res.val = vm.value("val").toInt();
        res.name1 = vm.value("name1").toString();
        res.name2 = vm.value("name2").toString();
        res.name3 = vm.value("name3").toString();
        res.name4 = vm.value("name4").toString();
        return res;
    }
    
    bool myStructEqual(MyStruct const &myStruct1, MyStruct const &myStruct2)
    {
        if (myStruct1.val != myStruct2.val) return false;
        if (myStruct1.name1 != myStruct2.name1) return false;
        if (myStruct1.name2 != myStruct2.name2) return false;
        if (myStruct1.name3 != myStruct2.name3) return false;
        if (myStruct1.name4 != myStruct2.name4) return false;
        return true;
    }
    

    As you can see, this QVariantMap approach is pretty cumbersome and obviously involves a lot of copying. But at least you can now access "the members" by their names in QML and you get the notifications when the MyStruct object changes.

        Forum.Car {
            id: myCar
        }
    
        Row {
            anchors.centerIn: parent
            spacing: 20
    
            Button {
                text: "click me"
                onClicked: {
                    var obj = myCar.myStruct // retrieve a copy of the struct
                    obj.val = 42 // set value in the copy
                    myCar.myStruct = obj // replace old struct with the copy
                }
            }
    
            Label {
                text: myCar.myStruct.val
            }
        }
    


  • @timday QStringList is directly supported by QML, no need to go through QVariant.



  • This post is deleted!


  • @Wieland Thanks!!! for your suggestion after lot of reading and searching I got the answer ,Actually we can use structs or any object which is not derived from QObject by using Q_GADGET :

    struct MyStruct {
        Q_GADGET
        int m_val;
        QString m_name1;
        QString m_name2;
        QString m_name3;
        QString m_name4;
        Q_PROPERTY(int val MEMBER m_val)
        Q_PROPERTY(QString name1 MEMBER m_name1)
        Q_PROPERTY(QString name2 MEMBER m_name2)
        Q_PROPERTY(QString name3 MEMBER m_name3)
        Q_PROPERTY(QString name4 MEMBER m_name4)
    };
    

    Then in my class i just replaced QVariant as below :

    class MyClass:public QObject
    {
        Q_OBJECT
        Q_PROPERTY(MyStruct mystr READ getMyStruct
                    WRITE setMyStruct NOTIFY myStructChanged)
    
    public:
        explicit MyClass(QObject *parent = nullptr);
        MyStruct strObj;
    
         // Edit: changed get function
         MyStruct getMyStruct() const
         {
             return strObj;
         }
    
    // Edit: Added set function
         void setMyStruct(myStruct val)
            {
                mystr = val;
                emit myStructChanged();
            }
    signals:
    void myStructChanged();
    
    }
    

    Now in QML file i can just use classObj.mystr.name1 to access the members and i can just use classObj.mystr.name1 = "abc" to set the values.



  • @pra7 Yes, that's possible, too. Just note that with that solution, MyStruct won't send notifications when a member changes. This is how basic QML types (e.g. color) are implemented.



  • One more thing, you did this...

    struct MyStruct {
        Q_GADGET 
        int m_val;
        QString m_name1;
        QString m_name2;
        QString m_name3;
        QString m_name4;
        Q_PROPERTY(int val MEMBER m_val)
        Q_PROPERTY(QString name1 MEMBER m_name1)
        Q_PROPERTY(QString name2 MEMBER m_name2)
        Q_PROPERTY(QString name3 MEMBER m_name3)
        Q_PROPERTY(QString name4 MEMBER m_name4)
    };
    

    ... where you put Q_GADGET in the public section. This has the side effect that all the members that come after Q_GADGET are private now. That's because Q_GADGET is a macro that expands to...

    #define Q_GADGET \
    public: \
        static const QMetaObject staticMetaObject; \
        void qt_check_for_QGADGET_macro(); \
        typedef void QtGadgetHelper; \
    private: \
        QT_WARNING_PUSH \
        Q_OBJECT_NO_ATTRIBUTES_WARNING \
        Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
        QT_WARNING_POP \
        QT_ANNOTATE_CLASS(qt_qgadget, "") \
        /*end*/
    

    ... , see qobjectdefs.h.

    So maybe better follow the usual convention to avoid surprises:

    class MyStruct {
        Q_GADGET 
        Q_PROPERTY(int val MEMBER m_val)
        Q_PROPERTY(QString name1 MEMBER m_name1)
        Q_PROPERTY(QString name2 MEMBER m_name2)
        Q_PROPERTY(QString name3 MEMBER m_name3)
        Q_PROPERTY(QString name4 MEMBER m_name4)
    public:
        int m_val;
        QString m_name1;
        QString m_name2;
        QString m_name3;
        QString m_name4;
    };
    


  • @Wieland Thanks !! for pointing that .



  • @Wieland Is there any possibility that I can access structure inside a structure?

    struct MyStruct {
    Q_GADGET
    int m_val;
    QString m_name1;
    QString m_name2;
    QString m_name3;
    QString m_name4;
    MyNewStruct m_newStr; //** new Struct includes Q_GADGET macro and member definations. 
    
    Q_PROPERTY(int val MEMBER m_val)
    Q_PROPERTY(QString name1 MEMBER m_name1)
    Q_PROPERTY(QString name2 MEMBER m_name2)
    Q_PROPERTY(QString name3 MEMBER m_name3)
    Q_PROPERTY(QString name4 MEMBER m_name4)
    
    Q_PROPERTY(MyNewStruct newStr MEMBER m_newStr) //**Currently getting error 
    };
    


  • @pra7 said in Best way to access a cpp structure in QML:

    //**Currently getting error

    What error?



  • This post is deleted!


  • @Wieland

    error: no match for 'operator!=' (operand types are 'myStruct1' and 'myStruct1') if (_t->mynewstr != *reinterpret_cast< myStruct1*>(_v)) {
    

    where Mystruct1 is myNewStruct and the error is in MOC ...



  • The MOC generates some code for you to implement all the Gadget / Property stuff. Looks like the generated code uses the != operator for MyStruct. So you need to implement that:

    public:
        bool operator==(MyStruct const &other) const;
        bool operator!=(MyStruct const &other) const;
    
    bool MyStruct::operator==(const MyStruct &other) const
    {
        // compare members 
        return true;
    }
    
    bool MyStruct::operator!=(MyStruct const &other) const
    {
        return !(*this == other);
    }
    


  • @Wieland That worked !!! Thanks, How to know that which all operators should be overloaded?



  • @pra7 said in Best way to access a cpp structure in QML:

    How to know that which all operators should be overloaded?

    You can't really know. But when the compiler complains about a missing operator, just implement it. In this case we didn't really need the == operator, but I'd say it's common practice to implement the != operator using the == operator.



  • @Wieland Thanks for all your suggestions.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.