来自 C++ 的 QQuickItem 实例化和设置难题(不允许将任何内容传递给构造函数)

Posted

技术标签:

【中文标题】来自 C++ 的 QQuickItem 实例化和设置难题(不允许将任何内容传递给构造函数)【英文标题】:QQuickItem instantiation from C++ and setup conundrum (not allowed to pass anything to the constructor) 【发布时间】:2014-01-07 02:09:52 【问题描述】:

我发现自己在尝试从 C++ 动态实例化自定义 QML 元素。

所以,布置问题:

我有一个从任何 UI 中抽象出来的轻量级 Node 对象数据结构。 由于QObject 和派生的内存很重,我必须按需实例化 UI 元素。 每个Node 都有一个_ui* 成员,每个UI 都有一个_node* 成员。 每个Node 都有一个唯一的_id,每个UI 都有一个ID PROPERTY,从_node* 传递。 ID 属性是只读的,因为 ID 是在 Node 实例化时设置的,不应修改。 QML 元素只能有带默认参数的构造函数,例如不能将任何东西传递给构造函数。 创建QML元素时,_node*成员是NULL,所以UI的任何QML子元素都不能访问ID属性,因为_node*在实例化时没有设置,它可以只能由 aux 方法设置,但由于该属性实际上是一个只读常量,因此在实际设置 UI_node* 成员时实例化后不再更新。

所以基本上,我需要能够在创建时将链接设置为每个UI 的相应Node,以便UI QML 元素可以访问它,但我不能将它传递给构造函数,并且由于该属性是只读的,因此在UI实例化时,当_node*UI成员仍然是NULL时,它只被读取一次,因此无法访问ID值。

想到的一个快速而肮脏的解决方案是在设置 _node* 成员后实例化时添加一个 NOTIFY idChanged() 信号以发出,即使 ID 属性从未真正改变,不能也不应该,并且添加对 ID getter 的检查 - 如果 _node*NULL,则返回一个假的虚拟任意值,否则从 _node* 成员获取 ID 值。不用说,这并不优雅,并且增加了一些开销,并且虚拟 ID 值是一个潜在的蠕虫罐,因此欢迎任何关于如何击败 QML 内部不良设计的想法。

【问题讨论】:

您究竟是如何从 C++ 实例化 QML 组件的?为什么构造函数中不能有参数? @SimonWarta - QQmlComponent c(_view->engine(), path); QObject * o = c.create(); - 组件由 QML 工厂从路径字符串实例化,无法指定构造函数参数。 仅供参考:当对象在 QML 中完成“创建”时,将调用虚拟方法 componentComplete()。我使用了一个标志,它允许我延迟初始化,直到对象完全初始化。在从 C++ 实例化时,我还没有弄清楚如何控制何时调用它,但它可能值得研究。 @Jay - 是的,后来我求助于创建对象,设置属性,然后调用completeCreate(),这对于您不需要在构建阶段进行设置的情况很有帮助. 【参考方案1】:

我刚刚提出了一个解决方案,而且是一个相当简单的解决方案。考虑到 QtQuick 的设计方式不允许指定构造函数参数,并且考虑到“创建和设置”方法在我的特定使用场景中具有巨大的影响,我决定简单地将值提供给 UI构造函数,即使没有作为参数传递。

因此,我只是执行“设置和创建”而不是“创建和设置”,并使用在创建每个项目之前设置并在项目构造函数中用于及时设置数据的静态类成员。

class UI : public QQuickItem

    Q_OBJECT
public:
    explicit UI(QQuickItem * parent = 0);
    ...
private:
    Object * object;
    static UI * rootUI;
;

Object * UI::protoObject = 0;

UI::UI(QQuickItem * parent) : QQuickItem(parent), object(protoObject) 
    if (!protoObject) qDebug() << "ERROR: prototype object is 0, unexpected";
    ...

以及实际的对象创建:

UI::setProtoObject(obj);
// create QQmlComponent with obj set being set in the constructor
UI::setProtoObject(0);

另外,当你不需要在构造函数中设置对象时适用的另一种方法是创建QQmlComponents beginCreate(),设置所需的属性,并以completeCreate()结束/p>

【讨论】:

【参考方案2】:

由于 QML 组件始终是没有特定数据的遗传结构,因此您无法避免两个步骤:实例化和填充。

ui.qml

import MyModules 1.0

MyUIRoot 
    id: root

    // all the visual stuff
    Rectangle 
        Text 
            text: root.nodeId // binding should be set up automatically
        
    

然后你在 C++ 中的 myuiroot.hmyuiroot.cpp 中构建一个 MyUIRoot 类,该类继承 QQuickItem 的函数

public:
    Q_PROPERTY(int nodeId READ nodeId NOTIFY nodeIdChanged);
    int nodeId();
    void setNode(Node* node);

signals:
    void nodeIdChanged();

private:
    Node* _node;

myuiroot.cpp 你有

MyUIRoot::MyUIRoot(QQuickItem *parent) :
    QQuickItem(parent)



int nodeId()

    if (_node)
        // make sure that every valid ID is > 0
        return _node.id();
    else
        return -1;


void setNode(Node* node)

    if (_node != node)
    
        _node = node;
        emit nodeIdChanged();
    

main.cpp注册您的组件

qmlRegisterType<MyUIRoot>("MyModules", 1, 0, "MyUIRoot");

并使用创建

QQmlComponent c(_view->engine(), path);
QObject *o = c.create();
MyUIRoot *item = qobject_cast<MyUIRoot*>(o);
item.setNode(....);

我看到的从 C++ 创建可视 Qt Quick 元素的唯一其他方法是使用“自定义场景图项”,您不需要 QML 来创建可视项。见http://qt-project.org/doc/qt-5.0/qtquick/qquickitem.html#custom-scene-graph-items。

【讨论】:

这正是我现在正在做的事情:QQmlComponent c(_view-&gt;engine(), path); QObject * o = c.create(); _ui = qobject_cast&lt;UI*&gt;(o); _ui-&gt;setObject(this); 但是在UI 实例化之后设置Node* 为时已晚,我需要能够设置Node*UI 的构造函数中,因此实例化的对象可以立即访问节点成员。有没有其他方法可以使用自定义构造函数而不是通过QQmlComponent::create() 来实例化QQuickItem 您的视觉UI 组件有多复杂?我可以想象,您可以使用 QQuickItems 及其子列表完全用 C++ 构建它。 UI 我们从 QQuickItem 派生而来,并且 100% 用 C++ 编写。它没有自己的视觉表示,我像 QML 的 Item 组件一样使用它 - 一个什么都不画但仍被视为视觉元素的组件,例如具有坐标和定位它所需的所有属性。这就是它来自QQuickItem 而不是直接来自QObject 的原因。 ThID 属性只是问题的一小部分,实际对象实际上有很多属性,并且还负责视觉表示的定位。无法立即访问Node 会产生一个巨大的问题,需要整个层次结构的额外更新周期才能发生正确的状态。我已经设法“解决”了无法在构造函数中设置模式的问题,但它非常难看并且是有代价的。唯一的解决方案是像普通 C++ 类一样实例化UI,并使用构造函数设置节点。【参考方案3】:

添加第二个构造函数并从 C++ 创建元素

ui.h

#include <QQuickItem>
#include "node.h"

class UI : public QQuickItem

    Q_OBJECT
public:
    explicit UI(QQuickItem *parent = 0);
    explicit UI(QQuickItem *parent = 0, Node *node = 0);

signals:

public slots:

private:
    Node* _node;

;

ui.cpp

#include "ui.h"

UI::UI(QQuickItem *parent) :
    QQuickItem(parent)



UI::UI(QQuickItem *parent, Node *node) :
    QQuickItem(parent)
  , _node(node)

    qDebug() << "My ID is" << node->id();

创建

Node *node = new Node();
UI ui(0, node); // or parent QQuickItem instead of '0'

【讨论】:

在堆栈上创建 UI? 另外,有一个问题 - 在我的设计中,我从来没有真正单独实例化 UI,它只是将它用作根对象的不同 QML 组件的功能基础(path 是从基于节点子类类型的 Q_ENUM 程序生成)。因此,创建UI 本身对我来说绝对没有好处,我需要能够创建任意 QML 文件,这些文件调用有一个 UI 元素作为每个节点要使用的根,并指定构造函数要设置的节点,这在我前一段时间发布的答案中得到了解决。

以上是关于来自 C++ 的 QQuickItem 实例化和设置难题(不允许将任何内容传递给构造函数)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 端获取 QQuickItem 的 C++ 对象实例 [重复]

C++ QQuickItem:如何在项目大小更改时触发函数

C++ template —— 实例化和模板实参演绎

如何将 X 和 Y 转换值应用于 C++ 端的 QQuickItem

创建 QQuickItem 子类的实例是不是存在我不打算呈现或添加到 QML 树的实例?

模板化运算符实例化和类型转换