using C++ classes and structs in QML
-
@J-Hilk that doesn't seem to matter any more. If you try to import a version number that's higher than what you registered, you'll get a runtime error, but that's about it.
This is just nuts, though. In main.cpp, this line:
qmlRegisterType<MyStruct>("myStruct", 1, 0, "myStruct");
Produces a warning from the editor/code model/whatever that QML types must begin with uppercase. Plus it won't build, giving an error at my QML declaration "error: Expected type name".
But if I modify that line in main.cpp to:
qmlRegisterType<MyStruct>("myStruct", 1, 0, "MyStruct"); // tried "myStruct" too
and then I have to change my Main.qml to:
property MyStruct myStruct1: MyStruct {
I get a runtime error: "qt.qml.typeregistration: Invalid QML element name "MyStruct"; value type names should begin with a lowercase letter"
It's almost as though I shouldn't be registering my struct, though I don't know what the alternative might be.
@mzimmers
I thought the Q_GADGET macro is used for meta-types only, which can't be instantiated from QML.
To be used e.g. if the struct is a property of an QObject derived class, which is somehow accessible in QML (either passed through C++ or created in QML).
Allows you to use e.g. Q_PROPERTY macros, without the QObject overhead.To be creatable from QML you need a derived class from QObject, so the Q_GADGET macro is not sufficient, as it is missing e.g. the signals and slots of the QObject class.
Note: I can be totally wrong / outdated, but this is how I was thinking and using it all the time :D
-
@mzimmers
I thought the Q_GADGET macro is used for meta-types only, which can't be instantiated from QML.
To be used e.g. if the struct is a property of an QObject derived class, which is somehow accessible in QML (either passed through C++ or created in QML).
Allows you to use e.g. Q_PROPERTY macros, without the QObject overhead.To be creatable from QML you need a derived class from QObject, so the Q_GADGET macro is not sufficient, as it is missing e.g. the signals and slots of the QObject class.
Note: I can be totally wrong / outdated, but this is how I was thinking and using it all the time :D
@lemons said in using C++ classes and structs in QML:
Q_GADGET macro
Lemons seems right.
In QML (Qt Meta-Object Language), the Q_GADGET macro is typically used with C++ classes to create non-instantiable classes that can be registered with the Qt meta-object system. These classes are similar to Q_OBJECT classes but cannot have signals, slots, or properties. They are often used for data-only structures that need to be exposed to QML.
I use upper case for my class registration in qmlRegisterType without issues. I guess lower case is needed in app_engine->rootContext()->setContextProperty( ... );
-
You can now instantiate Gadgets from QML with undocumented macros (so maybe don't use it).
Use
QML_VALUE_TYPE(typeName)
with a lower case name like you currently do.
Then you can addQML_CONSTRUCTIBLE_VALUE
orQML_STRUCTURED_VALUE
(even both).QML_CONSTRUCTIBLE_VALUE
will call a user defined constructorif you have
Q_INVOKABLE MyStruct(int foo) : m_myInt{foo} {}
then doingproperty myStruct myStruct1: 42
will call the constructor with 42 as the foo paramQML_STRUCTURED_VALUE
will assign properties depending on the js object you passed in QML.property myStruct myStruct1: ({myInt: 42})
will create a default constructed MyStruct and assign itsmyInt
property to 42.Note that the type should always be default constructible.
EDIT: Note that you don't have to use QML_ELEMENT, Q_DECLARE_METATYPE or qmlRegisterType if you use Q_GADGET and QML_VALUE_TYPE.
-
You can now instantiate Gadgets from QML with undocumented macros (so maybe don't use it).
Use
QML_VALUE_TYPE(typeName)
with a lower case name like you currently do.
Then you can addQML_CONSTRUCTIBLE_VALUE
orQML_STRUCTURED_VALUE
(even both).QML_CONSTRUCTIBLE_VALUE
will call a user defined constructorif you have
Q_INVOKABLE MyStruct(int foo) : m_myInt{foo} {}
then doingproperty myStruct myStruct1: 42
will call the constructor with 42 as the foo paramQML_STRUCTURED_VALUE
will assign properties depending on the js object you passed in QML.property myStruct myStruct1: ({myInt: 42})
will create a default constructed MyStruct and assign itsmyInt
property to 42.Note that the type should always be default constructible.
EDIT: Note that you don't have to use QML_ELEMENT, Q_DECLARE_METATYPE or qmlRegisterType if you use Q_GADGET and QML_VALUE_TYPE.
@GrecKo curiouser and curiouser.
My modified struct:
struct MyStruct { QML_STRUCTURED_VALUE QML_VALUE_TYPE(myStruct) Q_PROPERTY(int myInt MEMBER m_myInt) int m_myInt = 55; MyStruct() {} };
my registration (in main.cpp):
qmlRegisterType<MyStruct>("myStruct", 1, 0, "MyStruct");
and my QML:
property myStruct myStruct1: myStruct ({myInt: 100})
produces a runtime error: "myStruct is not a type."
Isn't using QML_VALUE_TYPE intended to allow me to use "myStruct" in the QML?
-
@GrecKo curiouser and curiouser.
My modified struct:
struct MyStruct { QML_STRUCTURED_VALUE QML_VALUE_TYPE(myStruct) Q_PROPERTY(int myInt MEMBER m_myInt) int m_myInt = 55; MyStruct() {} };
my registration (in main.cpp):
qmlRegisterType<MyStruct>("myStruct", 1, 0, "MyStruct");
and my QML:
property myStruct myStruct1: myStruct ({myInt: 100})
produces a runtime error: "myStruct is not a type."
Isn't using QML_VALUE_TYPE intended to allow me to use "myStruct" in the QML?
As I said : you don't have to use qmlRegisterType. You do have to use Q_GADGET though.
And Q_GADGET adds a
private:
so you need to add backpublic:
struct MyStruct { Q_GADGET QML_VALUE_TYPE(myStruct) QML_STRUCTURED_VALUE Q_PROPERTY(int myInt MEMBER m_myInt) public: int m_myInt = 55; MyStruct() {}; };
and my QML:
property myStruct myStruct1: myStruct ({myInt: 100})
Did I write that? nope. The syntax for structured values is :
property myStruct myStruct1: ({myInt: 100})
Ditch the
myStruct
on the right-hand side. -
As I said : you don't have to use qmlRegisterType. You do have to use Q_GADGET though.
And Q_GADGET adds a
private:
so you need to add backpublic:
struct MyStruct { Q_GADGET QML_VALUE_TYPE(myStruct) QML_STRUCTURED_VALUE Q_PROPERTY(int myInt MEMBER m_myInt) public: int m_myInt = 55; MyStruct() {}; };
and my QML:
property myStruct myStruct1: myStruct ({myInt: 100})
Did I write that? nope. The syntax for structured values is :
property myStruct myStruct1: ({myInt: 100})
Ditch the
myStruct
on the right-hand side.@GrecKo oh, that is a thing of beauty.
Now...about QML_STRUCTURED_VALUE being an undocumented macro, as you said...is it safe to use? I did notice mention of it in a bug report, so maybe it's OK.
Thanks for the help.
EDIT: everything above works, but if possible, I'd like to directly access enums defined in the struct (actually, this was the original point behind this entire thread).
struct MyStruct { Q_GADGET QML_STRUCTURED_VALUE QML_VALUE_TYPE(myStruct) Q_PROPERTY(int myInt MEMBER m_myInt) public: enum MyEnums { Enum0, Enum1, Enum2, Enum3 } m_myEnums; Q_ENUM(MyEnums) int m_myInt = 55; MyStruct() {} };
This attempt doesn't work:
Label { text: "myStruct.Enum3: " + myStruct.Enum3 }
(it doesn't work with "MyStruct" either.)
Can this be made to work? Thanks...
EDIT 2:
I have a workaround, which is to define my enums in a separate class and expose them to QML using the guidelines here, but if possible I'd prefer to avoid this level of indirection, and keep the enums in the struct.
-
@GrecKo oh, that is a thing of beauty.
Now...about QML_STRUCTURED_VALUE being an undocumented macro, as you said...is it safe to use? I did notice mention of it in a bug report, so maybe it's OK.
Thanks for the help.
EDIT: everything above works, but if possible, I'd like to directly access enums defined in the struct (actually, this was the original point behind this entire thread).
struct MyStruct { Q_GADGET QML_STRUCTURED_VALUE QML_VALUE_TYPE(myStruct) Q_PROPERTY(int myInt MEMBER m_myInt) public: enum MyEnums { Enum0, Enum1, Enum2, Enum3 } m_myEnums; Q_ENUM(MyEnums) int m_myInt = 55; MyStruct() {} };
This attempt doesn't work:
Label { text: "myStruct.Enum3: " + myStruct.Enum3 }
(it doesn't work with "MyStruct" either.)
Can this be made to work? Thanks...
EDIT 2:
I have a workaround, which is to define my enums in a separate class and expose them to QML using the guidelines here, but if possible I'd prefer to avoid this level of indirection, and keep the enums in the struct.
-
You can now instantiate Gadgets from QML with undocumented macros (so maybe don't use it).
Use
QML_VALUE_TYPE(typeName)
with a lower case name like you currently do.
Then you can addQML_CONSTRUCTIBLE_VALUE
orQML_STRUCTURED_VALUE
(even both).QML_CONSTRUCTIBLE_VALUE
will call a user defined constructorif you have
Q_INVOKABLE MyStruct(int foo) : m_myInt{foo} {}
then doingproperty myStruct myStruct1: 42
will call the constructor with 42 as the foo paramQML_STRUCTURED_VALUE
will assign properties depending on the js object you passed in QML.property myStruct myStruct1: ({myInt: 42})
will create a default constructed MyStruct and assign itsmyInt
property to 42.Note that the type should always be default constructible.
EDIT: Note that you don't have to use QML_ELEMENT, Q_DECLARE_METATYPE or qmlRegisterType if you use Q_GADGET and QML_VALUE_TYPE.
@GrecKo said in using C++ classes and structs in QML:
You can now instantiate Gadgets from QML with undocumented macros (so maybe don't use it).
Use
QML_VALUE_TYPE(typeName)
with a lower case name like you currently do.
Then you can addQML_CONSTRUCTIBLE_VALUE
orQML_STRUCTURED_VALUE
(even both).QML_CONSTRUCTIBLE_VALUE
will call a user defined constructorif you have
Q_INVOKABLE MyStruct(int foo) : m_myInt{foo} {}
then doingproperty myStruct myStruct1: 42
will call the constructor with 42 as the foo paramQML_STRUCTURED_VALUE
will assign properties depending on the js object you passed in QML.property myStruct myStruct1: ({myInt: 42})
will create a default constructed MyStruct and assign itsmyInt
property to 42.Note that the type should always be default constructible.
EDIT: Note that you don't have to use QML_ELEMENT, Q_DECLARE_METATYPE or qmlRegisterType if you use Q_GADGET and QML_VALUE_TYPE.
This is amazing !!
Gonna have to explore the possibilities... -
Coming back on what I said:
It is possible to expose an enum from a Q_GADGET.
If you add
pragma ValueTypeBehavior: Addressable
at the top of your QML file you can use the lower case gadget type name in QML and then doproperty int enumValue: myStruct.TestEnum.D
Alternatively you could do it very verbosely with QML_FOREIGN_NAMESPACE like explained here : https://codereview.qt-project.org/c/qt/qtdeclarative/+/510832 (fresh out of the oven)
-
@GrecKo said in using C++ classes and structs in QML:
Instead of defining your enums in a separate class I would do it in a namespace with Q_ENUM_NS instead.
namespace scheduleNS { Q_NAMESPACE enum StartAction { START_ACTION_TURN_ON, START_ACTION_TURN_OFF, START_ACTION_BE_READY, START_ACTION_SUSPEND }; Q_ENUM_NS(StartAction) } // namespace
but "scheduleNS" isn't recognized in my QML. How do I export a C++ namespace to QML?
-
@GrecKo said in using C++ classes and structs in QML:
Instead of defining your enums in a separate class I would do it in a namespace with Q_ENUM_NS instead.
namespace scheduleNS { Q_NAMESPACE enum StartAction { START_ACTION_TURN_ON, START_ACTION_TURN_OFF, START_ACTION_BE_READY, START_ACTION_SUSPEND }; Q_ENUM_NS(StartAction) } // namespace
but "scheduleNS" isn't recognized in my QML. How do I export a C++ namespace to QML?
@mzimmers said in using C++ classes and structs in QML:
How do I export a C++ namespace to QML?
I should have asked, is there a newer alternative to the old method of manually registering it like this:
qmlRegisterUncreatableMetaObject(scheduleNS::staticMetaObject, // static meta object "schedule.enums", // import statement 1, 0, // major and minor version of the import "ScheduleNS", // name in QML "Error: only enums"); // error in case someone tries to create a MyNamespace object
This works fine, but given that we no longer need to use qmlRegisterType(), I was wondering whether there was a more modern way of doing the qmlRegisterUncreatableMetaObject() call.
-
@GrecKo said in using C++ classes and structs in QML:
Instead of defining your enums in a separate class I would do it in a namespace with Q_ENUM_NS instead.
namespace scheduleNS { Q_NAMESPACE enum StartAction { START_ACTION_TURN_ON, START_ACTION_TURN_OFF, START_ACTION_BE_READY, START_ACTION_SUSPEND }; Q_ENUM_NS(StartAction) } // namespace
but "scheduleNS" isn't recognized in my QML. How do I export a C++ namespace to QML?
@mzimmers said in using C++ classes and structs in QML:
but "scheduleNS" isn't recognized in my QML. How do I export a C++ namespace to QML?
Have you tried naming the namespace with a capital letter?
As far as I recall only value types are supposed to be lower case. QML is somewhat picky on the namings. -
@mzimmers said in using C++ classes and structs in QML:
but "scheduleNS" isn't recognized in my QML. How do I export a C++ namespace to QML?
Have you tried naming the namespace with a capital letter?
As far as I recall only value types are supposed to be lower case. QML is somewhat picky on the namings.@kshegunov I did try that - I still have to register the uncreatable, and add an import statement to any QML wishing to use it.
Still a small price to pay...
-
@GrecKo said in using C++ classes and structs in QML:
Instead of defining your enums in a separate class I would do it in a namespace with Q_ENUM_NS instead.
namespace scheduleNS { Q_NAMESPACE enum StartAction { START_ACTION_TURN_ON, START_ACTION_TURN_OFF, START_ACTION_BE_READY, START_ACTION_SUSPEND }; Q_ENUM_NS(StartAction) } // namespace
but "scheduleNS" isn't recognized in my QML. How do I export a C++ namespace to QML?
@mzimmers said in using C++ classes and structs in QML:
but "scheduleNS" isn't recognized in my QML. How do I export a C++ namespace to QML?
With
QML_ELEMENT
orQML_NAMED_ELEMENT
. -
M mzimmers has marked this topic as solved on