Best way to access a cpp structure in QML
Asked Answered
B

2

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)

Bozuwa answered 12/8, 2017 at 12:28 Comment(0)
M
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)
};
Metalworking answered 12/8, 2017 at 12:42 Comment(16)
It worked !!! if i need to fill a QVariant from QML.How to do that .Can you please give an example ?Bozuwa
@Bozuwa what do you mean by "fill"? Create or simply assign values to members?Metalworking
Yes just assigning values to the members ,So that cpp can access the values set by QML.Bozuwa
Well, simply yourvar.val = 555.Metalworking
Thanks ,I just changed QVariant myVariant inside Q_PROPERTY to MyStruct and all return types to MyStruct. It worked.Bozuwa
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.Bozuwa
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.Metalworking
.Will try that .Bozuwa
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 ? }; Bozuwa
Yes, if it is a property or a value returned from a Q_INVOKABLE function. Otherwise you can't get to it from QML.Metalworking
I am getting error and I have posted as a new question please check : [link] (https://mcmap.net/q/1161355/-accessing-structure-inside-a-structure-in-qml/6336374)Bozuwa
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 providedStagger
@Stagger 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.Metalworking
@Metalworking 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.Stagger
But I need to use Q_DECLARE_METATYPE() and qRegisterMetaType<>() to register it in order to avoid unknown type error.Fokine
I would suggest you expand on your answer to be a complete working example. I think the 10% left to the user is proving problematicActinomycin
F
10
  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 ;)

Fokine answered 13/6, 2021 at 19:33 Comment(4)
This is a great description of how to use an existing struct, but what if the user simply wants to access the struct definition?Assuan
what do you mean @mzimmers? Maybe what you want, can be reached by full featured QObject based class instead.Fokine
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.Assuan
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.Fokine

© 2022 - 2024 — McMap. All rights reserved.