Как выставить указатель на Q_GADGET для QML через Q_PROPERTY

У меня есть Q_GADGET MyGadget определяется в файле 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*)

и Context класс, который содержит экземпляр MyGadget и предоставляет указатель на него в QML через 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};
};

Экземпляр Context создан в main и предоставляется QML как свойство контекста:

#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();
}

Файл QML, используемый с этим

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
    }
}

Все компилируется нормально, но при запуске не отображается текст и QML выдает предупреждение

qrc:/main.qml:9: TypeError: Невозможно прочитать свойство 'value' из null.

Если я удалю звонок qmlRegisterUncreatableType<MyGadget> в main и соответствующий import Test 1.0 в файле QML вместо этого отображается текст "Hello World undefined".

Единственный способ напечатать "Hello World 4", как и ожидалось, - это иметь Context::gadget вернуть копию сохраненного MyGadget объект вместо указателя на него, или сделать MyGadget Q_OBECT вместо. Но оба они не являются жизнеспособными вариантами в моем реальном приложении, так как здесь мне нужна ссылочная семантика, но в других местах я также хотел бы иметь семантику значений для класса, соответствующего MyGadget в этом примере.

Как я могу получить QML для чтения правильного значения свойства?

1 ответ

Решение

Гаджеты принципиально ограничены дизайном, вы не можете работать с ними по указателю из QML, вы должны работать с экземплярами, что также подразумевает передачу и возврат по значению. Это также означает, что любые ваши манипуляции применяются к копии, а не к реальному объекту, который вы намеревались.

Есть способ обойти это, используя PIMPL, и, по сути, объект гаджета должен быть указателем, то есть он имеет только одну переменную-член, которая является указателем на фактическую реализацию данных объекта, которая является отдельным объектом.

Это делает копирование объекта гаджета очень дешевым, поскольку это всего лишь указатель, и все копии ссылаются на одни и те же данные объекта, поэтому ваши изменения не будут потеряны.

Обновить:

В любом случае, если ваше свойство гаджета является просто int или каким-либо другим примитивом, как указано в вашем комментарии, вместо этого используйте его как свойство int. У вас могут быть свойства с уведомлениями, QML будет жаловаться на то, что они не имеют уведомлений, если вы используете их в привязках, но вы можете просто передать один фиктивный сигнал, который никогда не излучается, и вы настроены.

И даже если вы выбираете PIMPL, нет необходимости выделять его динамически на основе примитивов или вообще. Указатель не заботится о том, на что он указывает, кроме случаев, когда существует опасность, что память станет недействительной, а указатель - зависший.

Другие вопросы по тегам