2

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?

Corristo
  • 4,911
  • 1
  • 20
  • 36

1 Answers1

4

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.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • That is sad. I my real world application `Context` holds a large number of physical quantities (masses, forces, velocities,...) which are updated by simulation code. And once all of them are updated I'd like the `Context` object to inform QML to update the view. I explicitly don't want to update it on every change to one of the quantities, so they don't need signals. On the other hand, these essentially are only wrappers around primitive types, allocating them on the heap just to use PIMPL doesn't make sense in the rest of the application. – Corristo Apr 22 '17 at 13:46
  • Thinking about it, this seems more like an explicit design decision than a technical limitation, after all the behavior changes when I don't register `MyGadget*`. Do you happen to know why this decision was made? – Corristo Apr 22 '17 at 14:00
  • Read my answer update. It seems that (once again) you are trying to solve a problem the wrong way. I do not know why gadgets were implemented this way, a lot of Qt doesn't make sense to me either. I only recently discovered that, and tried pretty much everything I could think of http://stackoverflow.com/questions/42843942/q-gadget-vs-an-accessor-object-for-manipulating-a-polymorphic-tree-of-c-object before I found this https://bugreports.qt.io/browse/QTBUG-54321 – dtech Apr 22 '17 at 14:03
  • Yeah, I'll probably expose the primitive types in the properties of `Context` then. But I had hoped to declare the properties in QML with the correct types instead of primitive types to make it type safe and easier to read (e.g. `property Force` instead of `property int`) – Corristo Apr 22 '17 at 14:08
  • There is **no type safety** in QML. I mean, it will complain if you try to assign a string to an int or something like that, but without static typing that error will not become apparent until such an operation is attempted at runtime. QML actually goes even far from the reasonable, as you cannot even have a property of type T reference inside a QML T type, that will result in your application simply showing nothing, no error no warning... Another bug I found: https://bugreports.qt.io/browse/QTBUG-56292 – dtech Apr 22 '17 at 14:13