using C++ classes and structs in QML
-
@J-Hilk said in using C++ classes and structs in QML:
@mzimmers a struct and a class are literally the same in c++ 😉
Nope, there's key difference: the former has everything public by default while the latter has everything private.
@SGaist true enough, but I think I've obviated that difference with my use of the "public" keyword:
EDIT: I neglected to point out that the Q_GADGET macro causes everything declared after it to be private unless explicitly declared to the contrary, hence my use of public.struct MyStruct { Q_GADGET public: QML_ELEMENT QML_VALUE_TYPE(myStruct) Q_PROPERTY(int myInt MEMBER m_myInt) int m_myInt = 55; MyStruct() {} }; Q_DECLARE_METATYPE(MyStruct)
With this declaration, I get a build error on this line:
property myStruct myStruct1: myStruct {
"error: Expected type name"
-
@SGaist true enough, but I think I've obviated that difference with my use of the "public" keyword:
EDIT: I neglected to point out that the Q_GADGET macro causes everything declared after it to be private unless explicitly declared to the contrary, hence my use of public.struct MyStruct { Q_GADGET public: QML_ELEMENT QML_VALUE_TYPE(myStruct) Q_PROPERTY(int myInt MEMBER m_myInt) int m_myInt = 55; MyStruct() {} }; Q_DECLARE_METATYPE(MyStruct)
With this declaration, I get a build error on this line:
property myStruct myStruct1: myStruct {
"error: Expected type name"
-
@jsulm if you're referring to the QML line of code, I get an error if I try to use "MyStruct:"
@mzimmers can you show the full (copy pasted) content of your qml file ?
we haven't seen that and maybe its a simple error there@SGaist said in using C++ classes and structs in QML:
Nope, there's key difference: the former has everything public by default while the latter has everything private
mäh, tomato/tomato, it changes the default but you can still declare private /prublic manually
-
@mzimmers can you show the full (copy pasted) content of your qml file ?
we haven't seen that and maybe its a simple error there@SGaist said in using C++ classes and structs in QML:
Nope, there's key difference: the former has everything public by default while the latter has everything private
mäh, tomato/tomato, it changes the default but you can still declare private /prublic manually
@J-Hilk sure - I'll post from the top to the point of errors (I'm skipping the Labels that I use for telltales):
import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window import MyStruct Window { id: mainWindow width: 640 height: 480 visible: true property MyStruct myStruct1: MyStruct { myInt: 100 } MyStruct { id: myStruct2 myInt: 200 }
While I'm at it, here's my main.cpp ("struct" is the project name):
#include <QGuiApplication> #include <QQmlContext> #include <QQmlApplicationEngine> #include "mystruct.h" #include "myclass.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; qmlRegisterType<MyStruct>("MyStruct", 1, 0, "MyStruct"); // tried "myStruct" too qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass"); QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.loadFromModule("struct", "Main"); return app.exec(); }
-
@J-Hilk sure - I'll post from the top to the point of errors (I'm skipping the Labels that I use for telltales):
import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window import MyStruct Window { id: mainWindow width: 640 height: 480 visible: true property MyStruct myStruct1: MyStruct { myInt: 100 } MyStruct { id: myStruct2 myInt: 200 }
While I'm at it, here's my main.cpp ("struct" is the project name):
#include <QGuiApplication> #include <QQmlContext> #include <QQmlApplicationEngine> #include "mystruct.h" #include "myclass.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; qmlRegisterType<MyStruct>("MyStruct", 1, 0, "MyStruct"); // tried "myStruct" too qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass"); QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.loadFromModule("struct", "Main"); return app.exec(); }
don't know if that changed with Qt6 but shouldn't this
@mzimmers said in using C++ classes and structs in QML:
import MyStruct
be
import MyStruct 1.0
?
-
don't know if that changed with Qt6 but shouldn't this
@mzimmers said in using C++ classes and structs in QML:
import MyStruct
be
import MyStruct 1.0
?
@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.
-
@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...