using C++ classes and structs in QML
-
this will be a progressive question, starting first with the simplest: what am I doing wrong here?
class MyClass : public QObject { Q_OBJECT QML_ELEMENT int m_classInt = 55; public: MyClass() {} Q_PROPERTY(int classInt MEMBER m_classInt) }; Q_DECLARE_METATYPE(MyClass)
and
Window { width: 640 height: 480 visible: true property MyClass ccc ColumnLayout { Label { text: ccc.classInt } } }
returns:
TypeError: Cannot read property 'classInt' of null
I must be overlooking something insanely obvious, but...what am I missing here?
Thanks...
EDIT:
Renamed title of topic to better reflect the current issue.
-
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.
-
Based on this documentation, you are missing
{}
to create the object. -
@SGaist I looked at the doc you referenced, and tried a few things. It appears that there are two ways to instantiate a C++ object in QML:
property MyClass myClass1: MyClass { classInt: 44 } MyClass { id: myClass2 classInt: 33 }
I don't know if these are identical "under the hood," but preliminary testing indicates that they both work.
Moreover, I found that I could access enums in the class with the following:
class MyClass : public QObject { Q_OBJECT QML_ELEMENT int m_classInt = 55; signals: Q_INVOKABLE void classIntChanged(); public: MyClass() {} Q_PROPERTY(int classInt MEMBER m_classInt NOTIFY classIntChanged) enum MyEnums { Enum0, Enum1, Enum2, Enum3 } m_myEnums; Q_ENUM(MyEnums) };
(I added the notify stuff to suppress a runtime warning.)
In the QML:Label { text: "MyClass.Enum2: " + MyClass.Enum2 }
What I eventually discovered, and which is rather obscurely documented here, is that the enum names must begin with uppercase or the QML doesn't recognize them.
The ability to use enums without actually instantiating an object is excellent news, and was actually my 2nd part of the question.
Now, for the 3rd and hopefully final part: using a struct instead of a class. I'm running into what seem like conflicting requirements here. If I name my struct with a starting uppercase letter, I get a runtime error on this line:
// main.cpp qmlRegisterType<MyStruct>("MyStruct", 1, 0, "MyStruct");
the error is:
qt.qml.typeregistration: Invalid QML element name "MyStruct"; value type names should begin with a lowercase letter
But if I rename my struct to "myStruct" and change this line:
qmlRegisterType<myStruct>("myStruct", 1, 0, "myStruct");
Then I get a build time error in my qml:
property myStruct myStructXXX: myStruct {}
"error: Expected type name"
It appears that I'm getting something mixed up in the registration, but I can't quite decode what. Any suggestions? Thanks...
-
@mzimmers said in using C++ class in QML:
@SGaist I looked at the doc you referenced, and tried a few things. It appears that there are two ways to instantiate a C++ object in QML:
property MyClass myClass1: MyClass { classInt: 44 } MyClass { id: myClass2 classInt: 33 }
I don't know if these are identical "under the hood," but preliminary testing indicates that they both work.
As I understand it, they are both essentially the same in terms of instantiating the object. The difference is that in the second case the instance is instantiated and added as a child of your containing component (much like any other nested QML component), whereas in the first case you are assigning the instance to a property of your component.
-
@J-Hilk said in using C++ class in QML:
did you forget to add Q_GADGET in your struct ?
No, but I must be doing something just as silly. I even converted my struct to a class, so now I have:
class MyStruct { Q_GADGET QML_ELEMENT Q_PROPERTY(int myInt MEMBER m_myInt) int m_myInt = 55; public: MyStruct() {} }; Q_DECLARE_METATYPE(MyStruct)
along with:
class MyClass : public QObject { Q_OBJECT QML_ELEMENT Q_PROPERTY(int classInt MEMBER m_classInt NOTIFY classIntChanged) int m_classInt = 55; signals: Q_INVOKABLE void classIntChanged(); public: MyClass() {} }; Q_DECLARE_METATYPE(MyClass)
and in main.cpp:
qmlRegisterType<MyStruct>("MyStruct", 1, 0, "MyStruct"); qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass");
The first line produces a runtime error: "Invalid QML element name "MyStruct"; value type names should begin with a lowercase letter" while the second line works fine.
Somewhere, I'm overlooking one of the magic Qt macros, but I sure can't see it.
EDIT:
It works if I subclass MyStruct from QObject. This is OK for this little example, but has implications for my real project. Somehow I'm not getting Q_GADGET properly set up.
-
@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"
-
@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(); }
-
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.
-
@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.
-
@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?