I have a Q_GADGET MyGadget
defined in a file mygadget.h
#include <QObject>
class MyGadget {
Q_GADGET
Q_PROPERTY(int value READ value CONSTANT)
public:
MyGadget() = default;
MyGadget(int i)
: _value{i}
{
}
int value() const
{
return _value;
}
private:
int _value{0};
};
Q_DECLARE_METATYPE(MyGadget)
Q_DECLARE_METATYPE(MyGadget*)
and a Context
class that holds an instance of MyGadget
and exposes a pointer to it to QML via a Q_PROPERTY:
#include <QObject>
#include "mygadget.h"
class Context : public QObject
{
Q_OBJECT
Q_PROPERTY(MyGadget* gadget READ gadget CONSTANT)
public:
explicit Context()
: QObject{nullptr}
{
}
MyGadget* gadget() {
return &_gadget;
}
private:
MyGadget _gadget{4};
};
An instance of Context
is created in main
and exposed to QML as a context property:
#include <QGuiApplication>
#include <QQuickView>
#include <QString>
#include <QQmlContext>
#include "context.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
Context c;
// register MyGadget
qmlRegisterUncreatableType<MyGadget>("Test", 1, 0, "MyGadget", "");
qRegisterMetaType<MyGadget*>(); // <- removing this doesn't change anything
// make Context instance a context propery
view.rootContext()->setContextProperty("context", &c);
// show QML
view.setSource(QUrl{QStringLiteral("qrc:/main.qml")});
view.show();
return app.exec();
}
The QML file used with this is
import QtQuick 2.5
import Test 1.0
Rectangle {
height: 600
width: 800
visible: true
Text {
text: qsTr("Hello World " + context.gadget.value)
anchors.centerIn: parent
}
}
Everything compiles fine, but when running it no text isn't shown and QML emits a warning
qrc:/main.qml:9: TypeError: Cannot read property 'value' of null.
If I remove the call to qmlRegisterUncreatableType<MyGadget>
in main
and the corresponding import Test 1.0
in the QML file, the text "Hello World undefined" is shown instead.
The only way I got it to print "Hello World 4" as expected is to have Context::gadget
return a copy of the stored MyGadget
object instead of a pointer to it, or to make MyGadget
a Q_OBECT
instead. But both of these are not viable options in my real application, since I need reference semantics here, but in other places I'd also like to have value semantics for the class corresponding to MyGadget
in this example.
How can I get QML to read the correct property value?
Gadgets are fundamentally limited by design, you cannot work on a pointer basis with them from QML, you have to work with instances, which also implies passing and returning by value. Which also means any manipulations you make are applied to the copy, not the actual object you intended.
There is a way to work around that, by using PIMPL, and essentially have the gadget object be a pointer, that is, it only has a single member variable that is a pointer to the actual object data implementation, which is a separate object.
This makes the copying of the gadget object very cheap, since it is just a pointer, and all copies refer to the same object data so your changes will not be lost.
Update:
At any rate, if your gadget property is simply an int or some other primitive as stated in your comment, then have it as an int property instead. You can have properties with notifications, QML will whine that they don't have notifications if you use them in bindings, but you can simply pass a single dummy signal that is never emitted and you are set.
And even if you go for PIMPL, it is not necessary for to allocate dynamically on a per-primitive basis or at all. The pointer doesn't care what it points to, unless there is danger for the memory to become invalid, and the pointer - dangling.