10

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 {
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)
};

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();

}

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.mystr.name1)

        //EDIT added more code to explain the use case.
        classObj.myStr.name1 = "abc"  //Calls setter 
        classObj.mystr.name2 = "ans" // Calls setter 
        }
    }
}

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?

[UPDATE] - Should also add MyStruct to Q_DECLARE_METATYPE as below: Q_DECLARE_METATYPE(MyStruct)

pra7
  • 834
  • 2
  • 21
  • 50

2 Answers2

17

You need meta data to access C++ objects from QML.

For non QObject derived, this is achieved by using the Q_GADGET macro, and exposing the members as properties:

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)
};
dtech
  • 47,916
  • 17
  • 112
  • 190
  • It worked !!! if i need to fill a QVariant from QML.How to do that .Can you please give an example ? – pra7 Aug 12 '17 at 12:54
  • @pra7 what do you mean by "fill"? Create or simply assign values to members? – dtech Aug 12 '17 at 12:56
  • Yes just assigning values to the members ,So that cpp can access the values set by QML. – pra7 Aug 12 '17 at 12:59
  • 1
    Well, simply `yourvar.val = 555`. – dtech Aug 12 '17 at 13:00
  • Thanks ,I just changed **QVariant myVariant** inside **Q_PROPERTY** to **MyStruct** and all return types to MyStruct. It worked. – pra7 Aug 12 '17 at 13:06
  • Is there any way that i can set all the members of structures at once ? Now if i use **classObj.mystr.name1 = "abc"** ,**classObj.mystr.name2 = "ans"** and so on in QML ,Everytime setter is called. Is there any way to overcome this ? I have updated my question ,Please check. – pra7 Aug 12 '17 at 13:47
  • 1
    No, there is no such thing possible. If it already works, you don't need to do anything additionally. If you want to save repetition, just write a function that sets everything that accepts target object and the 5 property values. – dtech Aug 12 '17 at 15:14
  • .Will try that . – pra7 Aug 12 '17 at 15:16
  • Is there anyway to access structure inside a structure ? like `struct MyStruct { Q_GADGET int m_val; Mystruct1 str1; //NEW struct Q_PROPERTY(int val MEMBER m_val) Q_PROPERTY(int newStr MEMBER str1) //**ANYWAY to do this ? }; ` – pra7 Aug 14 '17 at 19:22
  • Yes, if it is a property or a value returned from a `Q_INVOKABLE` function. Otherwise you can't get to it from QML. – dtech Aug 14 '17 at 19:37
  • I am getting error and I have posted as a new question please check : [link] (https://stackoverflow.com/q/45681872/6336374) – pra7 Aug 14 '17 at 19:42
  • I don't think the implicit default constructor works the way you have wrote it. I am getting this error for a struct with one member variable: `candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided` – pooya13 Nov 29 '20 at 03:49
  • @pooya13 I have no idea what you are doing, but it sounds like you as trying to use a constructor that doesn't exist. Double check your code. – dtech Nov 29 '20 at 06:19
  • @dtech I am trying to use your struct which has an implicitly defaulted constructor. This does not work as is since the Macros make that section private. I think you'd need to move the macros after the member variable declarations. – pooya13 Nov 29 '20 at 06:29
  • But I need to use `Q_DECLARE_METATYPE()` and `qRegisterMetaType<>()` to register it in order to avoid `unknown type` error. – S.M.Mousavi Jun 13 '21 at 19:06
  • 1
    I would suggest you expand on your answer to be a complete working example. I think the 10% left to the user is proving problematic – TSG Feb 05 '22 at 21:01
9
  1. your struct or simple class must have Q_GADGET as minimum
  2. you should declare properties in order to access from qml
  3. you must declare your struct/class by Q_DECLARE_METATYPE()
  4. you must register it using qRegisterMetaType<>() somewhere before loading qml file by engine such as main.cpp

so you will have something like this:

//review carefully
struct MyStruct {
    Q_GADGET    //<-- 1.
    Q_PROPERTY(QString str1 MEMBER m_str1)    //<-- 2.
public:    //<-- important
    QString m_str1;
};
Q_DECLARE_METATYPE(MyStruct)    //<-- 3.

and use somewhere:

class Controller : public QObject
{
    Q_OBJECT
public:
    explicit Controller(QObject *parent = nullptr);
    Q_INVOKABLE MyStruct setNewConfig(QString v);    //<-- e.g.
    //...
}

main.cpp

//...
qmlRegisterType<Controller>("AppKernel", 1, 0, "Controller");
qRegisterMetaType<MyStruct>();    //<-- 4.
//...
engine.load(url);
//...

so it is usable in qml
main.qml

//...
    Controller {
        id: con
    }
    FileDialog {
        id: fileDialog
        nameFilters: ["Config file (*)"]
        onAccepted: {
            var a = con.setNewConfig(file);
            console.log(a.str1);    //<-- yeah! it is here
        }
    }
//...

NOTE 1: Be careful, it seems that nested classes/struct not supported by Qt meta

NOTE 2: You can expose struct just like a class. Inherit from QObject and use Q_OBJECT. See this article from Evgenij Legotskoj

NOTE 3: Above instructions makes struct/class known to qml and you can access properties/members, but is not instantiable in qml. see Qt document

NOTE 4: Be aware that qmlRegisterType<>() method is marked as "obsolete" in Qt 5.15+. Keep yourself updated ;)

S.M.Mousavi
  • 5,013
  • 7
  • 44
  • 59
  • This is a great description of how to use an *existing* struct, but what if the user simply wants to access the struct definition? – mzimmers Jan 13 '23 at 14:29
  • what do you mean @mzimmers? Maybe what you want, can be reached by full featured `QObject` based class instead. – S.M.Mousavi Jan 14 '23 at 20:56
  • I should have said the struct declaration, not definition (so one can create a new instance from QML). I think it may be possible by properly declaring the struct and invoking the c'tor with a QML var assignment, but I haven't figured it all out yet. – mzimmers Jan 15 '23 at 23:21
  • I already mentioned it in the **NOTE 3**. You need to declare a full featured `Object` based struct/class with `Q_OBJECT` macro in order to make it instantiable in QML. So, you can use that new type freely. – S.M.Mousavi Jan 16 '23 at 13:50