如何通过 Q_PROPERTY 向 QML 公开指向 Q_GADGET 的指针

Posted

技术标签:

【中文标题】如何通过 Q_PROPERTY 向 QML 公开指向 Q_GADGET 的指针【英文标题】:How to expose a pointer to a Q_GADGET to QML through a Q_PROPERTY 【发布时间】:2017-04-22 13:16:54 【问题描述】:

我在文件 mygadget.h 中定义了一个 Q_GADGET MyGadget

#include <QObject>

class MyGadget 
    Q_GADGET
    Q_PROPERTY(int value READ value CONSTANT)

public:
    MyGadget() = default;
    MyGadget(int i) 
        : _valuei
    
    

    int value() const
    
        return _value;
    

private:
    int _value0;
;
Q_DECLARE_METATYPE(MyGadget)
Q_DECLARE_METATYPE(MyGadget*)

还有一个 Context 类,它拥有一个 MyGadget 的实例并通过 Q_PROPERTY 向 QML 公开指向它的指针:

#include <QObject>
#include "mygadget.h"

class Context : public QObject

    Q_OBJECT
    Q_PROPERTY(MyGadget* gadget READ gadget CONSTANT)
public:
    explicit Context()
        : QObjectnullptr
     
     

    MyGadget* gadget() 
        return &_gadget;
    
private:
    MyGadget _gadget4;
;

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(QUrlQStringLiteral("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: Cannot read property 'value' of null.

如果我删除了对mainqmlRegisterUncreatableType&lt;MyGadget&gt; 的调用以及QML 文件中对应的import Test 1.0,则会显示文本“Hello World undefined”。

我让它按预期打印“Hello World 4”的唯一方法是让Context::gadget返回存储的MyGadget对象的副本而不是指向它的指针,或者使MyGadget成为@987654337 @ 反而。但是这两个在我的实际应用程序中都不是可行的选项,因为我在这里需要引用语义,但在其他地方我也希望在这个例子中为与 MyGadget 对应的类有值语义。

如何让 QML 读取正确的属性值?

【问题讨论】:

【参考方案1】:

小工具从根本上受到设计的限制,您不能在 QML 中以指针为基础使用它们,您必须使用实例,这也意味着按值传递和返回。这也意味着您所做的任何操作都会应用于副本,而不是您想要的实际对象。

有一种方法可以解决这个问题,通过使用 PIMPL,并且本质上让小工具对象成为一个指针,也就是说,它只有一个成员变量,它是一个指向实际对象数据实现的指针,这是一个单独的对象。

这使得小工具对象的复制非常便宜,因为它只是一个指针,并且所有副本都引用相同的对象数据,因此您的更改不会丢失。

更新:

无论如何,如果您的小工具属性只是一个 int 或您评论中所述的其他原语,那么请将其作为一个 int 属性。您可以拥有带有通知的属性,如果您在绑定中使用它们,QML 会抱怨它们没有通知,但您可以简单地传递一个从不发出的单一虚拟信号并且您已设置。

即使您选择 PIMPL,也没有必要在每个基元的基础上动态分配或根本没有必要。指针不关心它指向什么,除非内存有变得无效的危险,并且指针 - 悬空。

【讨论】:

这很可悲。我的真实世界应用程序Context 拥有大量由模拟代码更新的物理量(质量、力、速度……)。一旦它们全部更新,我希望 Context 对象通知 QML 更新视图。我明确不想在每次更改一个数量时更新它,因此它们不需要信号。另一方面,这些本质上只是原始类型的包装器,将它们分配在堆上只是为了使用 PIMPL 在应用程序的其余部分中没有意义。 仔细想想,这似乎更像是一个明确的设计决策,而不是技术限制,毕竟当我不注册MyGadget* 时所有行为都会发生变化。你知道为什么会做出这个决定吗? 阅读我的答案更新。似乎(再一次)您试图以错误的方式解决问题。我不知道为什么小工具是以这种方式实现的,很多 Qt 对我来说也没有意义。我最近才发现,并尝试了几乎所有我能想到的***.com/questions/42843942/…,然后才找到bugreports.qt.io/browse/QTBUG-54321 是的,我可能会在Context 的属性中公开原始类型。但我曾希望用正确的类型而不是原始类型来声明 QML 中的属性,以使其类型安全且更易于阅读(例如 property Force 而不是 property int QML 中没有类型安全。我的意思是,如果您尝试将字符串分配给 int 或类似的东西,它会抱怨,但如果没有静态类型,该错误不会变得明显,直到在运行时尝试这样的操作。 QML 实际上甚至远非合理,因为您甚至不能在 QML T 类型中拥有类型 T 引用的属性,这将导致您的应用程序根本不显示任何内容,没有错误没有警告......我发现的另一个错误:@987654323 @

以上是关于如何通过 Q_PROPERTY 向 QML 公开指向 Q_GADGET 的指针的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ 向 QML 发出信号以读取 Q_PROPERTY 是同步事件吗?

如何将 QObject 指针的属性公开给 QML

在没有 Q_PROPERTY 定义的情况下从 C++ 访问 QML 对象的属性

Qt:如何在 C++ 端而不是 QML 上监视 Q_PROPERTY 更改

使用信号或 Q_PROPERTY 更新 QML 对象

如何使用 Q_PROPERTY 公开自定义对象列表