C++定义QML类型

当用C++代码扩展QML时,可以将C++类注册到QML类型系统中,使该类能在QML代码中用作数据类型。虽然任何QObject派生类的属性、方法和信号都可从QML访问,如在QML中公开C++类型属性中所述,但此类不能在QML中使用,直到它在类型系统中注册。此外,注册还可以提供其他功能,如允许类在QML中用作可实例化的QML对象类型,或使类的单例实例可以从QML中导入和使用。

此外,Qt Qml模块提供了在C++中实现QML特定功能(如附加属性默认属性)的机制。

(请注意,本文档中涉及的一些重要概念在使用C++编写QML扩展教程中进行了演示。)

注意:所有声明QML类型的头文件都需要从项目的包含路径中无前缀可访问。

有关C++和不同QML集成方法的信息,请参阅C++和QML集成概述页面。

使用QML类型系统注册C++类型

QObject派生类可以注册到QML类型系统,以便在QML代码中将其用作数据类型。

引擎允许注册可实例化和不可实例化的类型。注册可实例化的类型将C++类用作QML对象类型的定义,允许它用于从QML代码中创建对象的声明。注册还向引擎提供了额外的类型元数据,使类型(以及由该类声明的任何枚举)可以用于属性值、方法参数和返回值以及QML和C++之间交换的信号参数。

注册不可实例化的类型也以这种方式将类注册为数据类型,但是类型不能用作从QML实例化的QML对象类型。例如,如果一个类型有应该被QML公开的枚举,而这个类型本身不应被实例化时,这样做很有用。

有关选择正确方法公开C++类型到QML的快速指南,请参阅选择C和QML之间正确的集成方法

先决条件

以下所有宏都来自qqmlregistration.h头文件。您需要将以下代码添加到使用它们的文件中,以便使宏可用

#include <QtQml/qqmlregistration.h>

此外,您的类声明必须位于可以通过您项目的包含路径访问的头部文件中。这些声明用于在编译时生成注册代码,并且注册代码需要包含包含声明的头部文件。

注册可实例化对象类型

任何QObject派生的C++类都可以注册为QML对象类型的定义。一旦一个类在QML类型系统中注册,它就可以被声明和实例化,就像任何来自QML代码的其他对象类型一样。一旦创建,类实例可以从QML中进行操作;如将C++类型属性暴露给QML所述,任何QObject派生类的属性、方法和信号都可以从QML代码中访问。

要将QObject派生类注册为可实例化的QML对象类型,请将QML_ELEMENTQML_NAMED_ELEMENT(<name>)添加到类声明中。您还需要在构建系统中进行一些调整。对于qmake,请在项目文件中添加CONFIG += qmltypesQML_IMPORT_NAMEQML_IMPORT_MAJOR_VERSION。对于CMake,包含该类的文件应该通过qt_add_qml_module()成为某个目标设置的一部分。这将使用类名或显式给定的名称作为QML类型名称,将该类注册到给定主要版本下的类型命名空间中。次要版本将由属性、方法或信号中附带的任何修订版推导而来。默认次要版本是0。您可以通过将QML_ADDED_IN_VERSION()宏添加到类声明中来显式限制类型只能从特定的次要版本中进行访问。客户端可以导入命名空间的相应版本以使用该类型。

例如,假设有一个具有authorcreationDate属性的Message

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
    Q_PROPERTY(QDateTime creationDate READ creationDate WRITE setCreationDate NOTIFY creationDateChanged)
    QML_ELEMENT
public:
    // ...
};

此类可以通过向项目文件添加适当的数据命名空间和版本号进行注册。例如,要使类型在com.mycompany.messaging命名空间中可用,版本为1.0

qt_add_qml_module(messaging
    URI com.mycompany.messaging
    VERSION 1.0
    SOURCES
        message.cpp message.h
)
CONFIG += qmltypes
QML_IMPORT_NAME = com.mycompany.messaging
QML_IMPORT_MAJOR_VERSION = 1

如果该类声明的头文件无法从您项目的包含路径中访问,您可能需要修改包含路径,以便编译生成的注册代码。

INCLUDEPATH += com/mycompany/messaging

类型可以在QML的对象声明中使用,其属性可以按照以下示例进行读取和写入

import com.mycompany.messaging

Message {
    author: "Amelie"
    creationDate: new Date()
}

注册值类型

任何具有Q_GADGET宏的类型都可以注册为QML值类型。一旦此类与QML类型系统注册,就可以在QML代码中用作属性类型。此类实例可以从QML中进行操作;如将C++类型属性暴露给QML所述,任何值类型的属性和方法都可以从QML代码中访问。

与对象类型相比,值类型需要小写名称。注册它们的首选方式是使用QML_VALUE_TYPEQML_ANONYMOUS宏。由于您的C++类通常具有大写名称,因此没有与QML_ELEMENT相当的宏。否则,注册与对象类型的注册非常相似。

例如,假设您想要注册一个由两个字符串组成的值类型person,一个是姓氏,另一个是名字

class Person
{
    Q_GADGET
    Q_PROPERTY(QString firstName READ firstName WRITE setFirstName)
    Q_PROPERTY(QString lastName READ lastName WRITE setLastName)
    QML_VALUE_TYPE(person)
public:
    // ...
};

对值类型还可以做的一些限制更多

  • 值类型不能是单例。
  • 值类型需要默认构造和拷贝构造。
  • QProperty用作值类型的成员是一个问题。值类型会被复制,这时你需要决定如何处理QProperty上的任何绑定。你不应在值类型中使用QProperty
  • 值类型无法提供附加属性。
  • 定义值类型扩展的API(《a href="qqmlengine.html#QML_EXTENDED" translate="no">QML_EXTENDED)并非公开且可能随时更改。

带有枚举的值类型

将值类型中的枚举公开到QML需要一些额外的步骤。

在QML中,值类型的名称为小写,且通常JavaScript代码无法访问具有小写名称的类型(除非指定pragma ValueTypeBehavior: Addressable)。如果你有一个C++中的值类型,并希望将其枚举公开到QML,你需要单独公开枚举。

这可以通过使用QML_FOREIGN_NAMESPACE来解决。首先,从你的值类型派生出一个新的C++类型

class Person
{
    Q_GADGET
    Q_PROPERTY(QString firstName READ firstName WRITE setFirstName)
    Q_PROPERTY(QString lastName READ lastName WRITE setLastName)
    QML_VALUE_TYPE(person)
public:
    enum TheEnum { A, B, C };
    Q_ENUM(TheEnum)
    //...
};

class PersonDerived: public Person
{
    Q_GADGET
};

然后将派生类型作为外部名称空间公开

namespace PersonDerivedForeign
{
    Q_NAMESPACE
    QML_NAMED_ELEMENT(Person)
    QML_FOREIGN_NAMESPACE(PersonDerived)
}

这会产生一个名为Person(大写)的QML名称空间,其中包含名为TheEnum的枚举和值ABC。然后你可以在QML中编写以下内容

someProperty: Person.A

同时,你仍然可以像以前一样准确地使用名为person(小写)的值类型。

注册不可创建的类型

有时,一个QObject派生类可能需要向QML类型系统注册,但不是作为一个可创建的类型。例如,如果一个C++类

  • 是一个不应该实例化的接口类型
  • 是一个不需要暴露给QML的基类类型
  • 声明了某些枚举,应该可以从QML访问,但其他方面不应该可以实例化
  • 是一个应该通过单例实例提供给QML的类型,并且不应该可以从QML中创建实例

Qt Qml模块提供了几个宏用于注册不可创建的类型

  • QML_ANONYMOUS注册了一个不可创建的C++类型,并且无法从QML引用。这允许引擎将任何可以由QML创建的继承类型强制转换。
  • QML_INTERFACE注册了一个现有的Qt接口类型。这个类型无法从QML中创建实例,并且你不能使用它声明QML属性。不过,从QML使用此类C++属性会执行期望的接口转换。
  • QML_UNCREATABLE(reason)结合QML_ELEMENTQML_NAMED_ELEMENT注册了一个所谓的C++类型,该类型不可创建,但对于QML类型系统可识别。如果类型枚举或附加属性应从QML中访问,但类型本身不应可以从QML创建实例,这将很有用。该参数是如果检测到尝试创建类型实例的尝试时将发出的错误消息。
  • QML_SINGLETON结合QML_ELEMENTQML_NAMED_ELEMENT注册了一个单例类型,可以根据下面的讨论从QML导入。

请注意,所有注册到QML类型系统的C++类型必须是QObject派生类,即使它们是不可创建的。

使用单例类型注册单例对象

单例类型允许在命名空间中公开属性、信号和方法,而不需要客户端手动实例化对象。特别是,QObject 单例类型是提供功能或全局属性值的有效且便捷的方式。

请注意,单例类型没有关联的 QQmlContext,因为它们在引擎的各个上下文中是共享的。 QObject 单例类型的实例由 QQmlEngine 构建,并由其拥有,当引擎被销毁时,将销毁这些实例。

可以像操作任何其他的 QObject 或实例化类型一样与 QObject 单例类型交互,但只有一个实例(由引擎构建并拥有)存在,并且必须通过类型名称而不是 ID 进行引用。 QObject 单例类型的 Q_PROPERTY 可以绑定,并且 Q_INVOKABLEQObject 模块 API 函数可以在信号处理程序表达式中使用。这使得单例类型成为实现样式或主题的理想方式,并且它们还可以用于存储全局状态或提供全局功能,而不是使用 ".pragma library" 脚本导入。

一旦注册,QObject 单例类型就可以像任何其他 QML 中公开的 QObject 实例一样导入和使用。下面的示例假设将 QObject 单例类型注册到了版本为1.0的 "MyThemeModule" 命名空间中,其中该QObject 包含一个 QCOLOR 类型的 "color" Q_PROPERTY

import MyThemeModule 1.0 as Theme

Rectangle {
    color: Theme.color // binding.
}

也可以将 QJSValue 作为单例类型公开,但是客户端应该注意,无法绑定此类类型的属性。

有关如何实现和注册新的单例类型以及如何使用现有单例类型的信息,请参阅 QML_SINGLETON。有关单例的更深入信息,请参阅 QML中的单例

注意:在QML中注册的类型枚举值应从大写字母开始。

最终属性

使用 FINAL 修饰符声明的最终属性不能被覆盖。这意味着在继承类型中,无论是 QML 中声明的还是 C++ 中声明的同名属性或函数,都将被 QML 引擎忽略。建议尽可能声明属性 FINAL,以避免意外的覆盖。属性的覆盖不仅在派生类中可见,而且在执行基类上下文的 QML 代码中也是可见的。这类 QML 代码通常期望原始属性,但这是一个常见的错误来源。

声明为 FINAL 的属性也不能被 QML 中的函数或 C++ 中的 Q_INVOKABLE 方法覆盖。

类型修订和版本

许多类型注册函数需要指定注册类型的版本。类型修订和版本允许在新的版本中存在新的属性或方法,同时与旧版本保持兼容。

考虑以下两个 QML 文件

// main.qml
import QtQuick 1.0

Item {
    id: root
    MyType {}
}
// MyType.qml
import MyTypes 1.0

CppType {
    value: root.x
}

其中 CppType 映射到 C++ 类 CppType

如果 CppType 的作者在其类型定义的新版本中为 CppType 添加了 root 属性,那么 root.x 现在会解析为不同的值,因为 root 也是顶层组件的 id。作者可以指定新的 root 属性从特定的小版本开始可用。这使得在没有打破现有程序的情况下向现有类型添加新的属性和功能成为可能。

使用 REVISION 标签将 root 属性标记为在类型的版本 1 中添加。如 Q_INVOKABLE 的方法、信号和槽也可以通过使用 Q_REVISION(x) 宏进行修订。

class CppType : public BaseType
{
    Q_OBJECT
    Q_PROPERTY(int root READ root WRITE setRoot NOTIFY rootChanged REVISION 1)
    QML_ELEMENT

signals:
    Q_REVISION(1) void rootChanged();
};

这种方式给出的修订将自动解释为主要版本文件中给出的主要版本的小版本。在这种情况下,只有当导入 MyTypes 版本 1.1 或更高时,root 才可用。导入 MyTypes 版本 1.0 则不受影响。

出于相同的原因,在后续版本中引入的新类型应该使用 QML_ADDED_IN_VERSION 宏进行标记。

这种语言特性允许在不破坏现有应用的情况下做出行为变更。因此,QML 模块作者应始终记得记录小版本之间的更改,并且 QML 模块用户在部署更新的导入语句之前应检查其应用程序是否仍然正确运行。

当注册类型本身时,您所依赖的基本类的修订版本会自动注册。当从其他作者提供的基类中派生时非常有用,例如在扩展 Qt Quick 模块中的类时。

注意:QML 引擎不支持分组和附加属性对象的属性或信号的重写。

注册扩展对象

当将现有类和技术集成到 QML 中时,API 需要调整以更好地适应声明性环境。尽管通常通过直接修改原始类可以获得最佳结果,但如果这不可能或因其他问题而变得复杂,扩展对象允许在不直接修改的情况下提供有限的扩展。

扩展对象 向现有类型添加附加属性。扩展类型定义允许程序员在注册类时提供额外的类型,称为 扩展类型。它的成员从 QML 中使用时可以透明地合并到原始目标类中。例如

QLineEdit {
    leftMargin: 20
}

新的 leftMargin 属性是添加到现有 C++ 类型 QLineEdit 的属性,而无需修改其源代码。

QML_EXTENDED(扩展) 宏用于注册扩展类型。参数是作为扩展使用的另一个类的名称。

您还可以使用 QML_EXTENDED_NAMESPACE(命名空间) 注册一个命名空间,特别是其中声明的枚举作为类型的扩展。如果您要扩展的类型本身是命名空间,您需要使用 QML_NAMESPACE_EXTENDED(namespace)。

扩展类是一个常规的 QObject,其构造函数接受一个 QObject 指针。然而,扩展类的创建会延迟到第一次访问扩展属性。创建扩展类并将目标对象作为父对象传递。当访问原始属性时,将使用扩展对象上的相应属性。

注册外国类型

可能存在一些C++类型,无法修改以包含上述宏。这些可能来自第三方库的类型,或者需要履行某种与传统宏存在冲突的合同。尽管如此,您仍然可以使用QML_FOREIGN宏将这些类型暴露给QML。为此,创建一个仅包含注册宏的单独结构体,如下所示:

// Contains class Immutable3rdParty
#include <3rdpartyheader.h>

struct Foreign
{
    Q_GADGET
    QML_FOREIGN(Immutable3rdParty)
    QML_NAMED_ELEMENT(Accessible3rdParty)
    QML_ADDED_IN_VERSION(2, 4)
    // QML_EXTENDED, QML_SINGLETON ...
};

从这段代码中,您可以得到一个具有Immutable3rdParty方法和属性的QML类型,以及Foreign中指定的QML特性(例如:singleton,extended)。

定义QML特定的类型和属性

提供附加属性

在QML语言语法中,有一个关于附加属性和附加信号处理器的概念,这些是附加到对象上的额外属性。从本质上讲,这样的属性是由一个附加类型提供的,并且这些属性可以附加到另一个类型的对象上。这与由对象类型本身(或对象继承的类型)提供的一般对象属性形成对比。

例如,下面的Item使用了附加属性和附加处理器

import QtQuick 2.0

Item {
    width: 100; height: 100

    focus: true
    Keys.enabled: false
    Keys.onReturnPressed: console.log("Return key was pressed")
}

在此,Item对象能够访问和设置代码中的值Keys.enabledKeys.onReturnPressed。这样可以允许Item对象将其自身的现有属性扩展到这些额外属性。

实现附加对象步骤

在考虑上述示例时,有一些相关方参与其中

  • 有一个匿名的附加对象类型实例,它包含一个enabled和一个returnPressed信号,已附加到Item对象上,使其能够访问和设置这些属性。
  • Item对象是附加者,附加对象类型的实例已附加到它。
  • Keys附加类型,它通过命名限定符“Keys”为附加者提供访问附加对象类型属性的方法。

当QML引擎处理此代码时,它会创建附加对象类型的单例实例,并将其附加到Item对象上,因此它能够访问该实例的enabledreturnPressed属性。

通过提供附加对象类型的类和附加类型类,可以从C++实现提供附加对象的机制。对于附加对象类型,提供一个从QObject派生的类,该类定义了要使附加者对象可访问的属性。对于附加类型,提供一个从QObject派生的类,它在其中实现一个静态的qmlAttachedProperties(),其签名如下:

  • static <AttachedPropertiesType> *qmlAttachedProperties(QObject *object);

    此方法应返回附加对象类型的实例。

    QML引擎调用此方法以将附加对象类型的实例附加到由object参数指定的附加者。尽管这样做是常规做法,但不严格要求,此方法的实现应将返回的实例成为object的父级,以防止内存泄漏。

    该方法最多被引擎调用一次每个附件对象实例,因为引擎将为后续的附加属性访问缓存返回的实例指针。因此,附件对象可能在相应的附件 对象被销毁之前不会被删除。

  • 可以这样声明为一个附加类型,通过将QML_ATTACHED(附加)宏添加到类声明中。参数是附加对象类型的名称。

实现附加对象:示例

例如,考虑在一个早期例子中描述的Message类型。

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
    Q_PROPERTY(QDateTime creationDate READ creationDate WRITE setCreationDate NOTIFY creationDateChanged)
    QML_ELEMENT
public:
    // ...
};

假设当信息发布到信息板时,需要在Message上触发一个信号,并且跟踪信息在信息板上过期。因为这些属性直接添加到Message不合适,所以它们可以作为通过“MessageBoard”修饰符提供的附加属性在Message对象上实现。根据之前描述的概念,涉及的各方包括:

  • 一个匿名附加对象类型的实例,它提供了一个published信号和一个过期属性。这个类型在下面的MessageBoardAttachedType中实现。
  • Message对象,这将是附件
  • MessageBoard类型,这将是被Message对象使用的用于访问附加属性的附加类型

以下是一个示例实现。首先,需要一个附加对象类型,它具有可供附件访问的必要属性和信号。

class MessageBoardAttachedType : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool expired READ expired WRITE setExpired NOTIFY expiredChanged)
    QML_ANONYMOUS
public:
    MessageBoardAttachedType(QObject *parent);
    bool expired() const;
    void setExpired(bool expired);
signals:
    void published();
    void expiredChanged();
};

然后,附加类型MessageBoard必须声明一个返回由MessageBoardAttachedType实现的附加对象类型实例的qmlAttachedProperties()方法。此外,MessageBoard必须通过QML_ATTACHED()宏声明为附加类型。

class MessageBoard : public QObject
{
    Q_OBJECT
    QML_ATTACHED(MessageBoardAttachedType)
    QML_ELEMENT
public:
    static MessageBoardAttachedType *qmlAttachedProperties(QObject *object)
    {
        return new MessageBoardAttachedType(object);
    }
};

现在,Message类型可以访问附加对象类型的属性和信号。

Message {
    author: "Amelie"
    creationDate: new Date()

    MessageBoard.expired: creationDate < new Date("January 01, 2015 10:45:00")
    MessageBoard.onPublished: console.log("Message by", author, "has been
published!")
}

此外,C++实现可以通过调用qmlAttachedPropertiesObject()函数访问附加到任何对象的附加对象实例。

例如

Message *msg = someMessageInstance();
MessageBoardAttachedType *attached =
        qobject_cast<MessageBoardAttachedType*>(qmlAttachedPropertiesObject<MessageBoard>(msg));

qDebug() << "Value of MessageBoard.expired:" << attached->expired();

传递附加属性

QQuickAttachedPropertyPropagator可以继承以从父对象传播附加属性到其子对象,类似于字体调色板传播。它支持通过项目弹出窗口进行传播。

属性修饰符类型

属性修饰符类型是一种特殊的QML对象类型。属性修饰符类型实例会影响其应用的属性(QML对象实例的属性)。有两种不同类型的属性修饰符类型

  • 属性值写入拦截器
  • 属性值来源

属性值写入拦截器可以用来过滤或修改写入属性的值。目前,唯一的支持的属性值写入拦截器是由QtQuick导入提供的Behavior类型。

属性值源可以用来在一段时间内自动更新属性值。客户端可以定义自己的属性值源类型。《QtQuick》导入提供的各种属性动画类型就是属性值源的示例。

可以通过“<ModifierType> on <propertyName>”语法创建属性修饰器类型实例,并将其应用到QML对象的属性上,以下是一个示例。

import QtQuick 2.0

Item {
    width: 400
    height: 50

    Rectangle {
        width: 50
        height: 50
        color: "red"

        NumberAnimation on x {
            from: 0
            to: 350
            loops: Animation.Infinite
            duration: 2000
        }
    }
}

这通常被称为“on”语法。

客户端可以注册自己的属性值源类型,但当前不能注册属性值写入拦截器。

属性值源

属性值源是QML类型,可以自动在一段时间内更新属性值,使用<PropertyValueSource> on <property>语法。例如,《QtQuick》模块提供的各种属性动画类型就是属性值源的示例。

可以通过继承QQmlPropertyValueSource并在C++中提供实现来在C++中实现属性值源,该实现可以在一段时间内将不同的值写入不同的属性。当使用QML中的<PropertyValueSource> on <property>语法将属性值源应用到属性上时,引擎会提供对该属性的引用,以便更新属性值。

例如,假设有一个叫做 RandomNumberGenerator 的类可以这样实现,使得当它被用作属性值源应用到QML属性时,它会每500毫秒更新属性值为一个新的随机数。还可以为这个随机数生成器提供一个最大值。该类可以按以下方式实现:

class RandomNumberGenerator : public QObject, public QQmlPropertyValueSource
{
    Q_OBJECT
    Q_INTERFACES(QQmlPropertyValueSource)
    Q_PROPERTY(int maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged);
    QML_ELEMENT
public:
    RandomNumberGenerator(QObject *parent)
        : QObject(parent), m_maxValue(100)
    {
        QObject::connect(&m_timer, SIGNAL(timeout()), SLOT(updateProperty()));
        m_timer.start(500);
    }

    int maxValue() const;
    void setMaxValue(int maxValue);

    virtual void setTarget(const QQmlProperty &prop) { m_targetProperty = prop; }

signals:
    void maxValueChanged();

private slots:
    void updateProperty() {
        m_targetProperty.write(QRandomGenerator::global()->bounded(m_maxValue));
    }

private:
    QQmlProperty m_targetProperty;
    QTimer m_timer;
    int m_maxValue;
};

当QML引擎遇到将 RandomNumberGenerator 作为属性值源使用的情况时,它会调用 RandomNumberGenerator::setTarget() 来提供类型和值源所应用的属性。当 RandomNumberGenerator 内部的定时器每500毫秒触发时,它将写入一个新的数值到指定的属性。

一旦 RandomNumberGenerator 类注册到QML类型系统中,它就可以作为属性值源在QML中使用。以下示例显示它如何每隔500毫秒改变一个 Rectangle 的宽度。

import QtQuick 2.0

Item {
    width: 300; height: 300

    Rectangle {
        RandomNumberGenerator on width { maxValue: 300 }

        height: 100
        color: "red"
    }
}

除了上述情况外,属性值源是常规的QML类型,具有属性、信号、方法等,但增加了可以使用<PropertyValueSource> on <property>语法改变属性值的功能。

当把属性值源对象分配给属性时,QML首先会像常规QML类型一样进行分配。只有在此分配失败时,引擎才会调用setTarget()方法。这允许该类型不仅仅在值源之外的环境中也被使用。

为QML对象类型指定默认和父属性

任何注册为可实例化QML对象类型的QObject派生类型都可以选择为该类型指定一个默认属性。默认属性是在对象子代未指定分配给任何特定属性时自动分配给子代的属性。

可以通过为具有特定“DefaultProperty”值的类调用 Q_CLASSINFO() 宏来设置默认属性。例如,下面的 MessageBoard 类将其 messages 属性指定为类的默认属性

class MessageBoard : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
    Q_CLASSINFO("DefaultProperty", "messages")
    QML_ELEMENT
public:
    QQmlListProperty<Message> messages();

private:
    QList<Message *> m_messages;
};

这允许 MessageBoard 对象的子对象在未分配到特定属性的情况下自动分配给其 messages 属性。例如

MessageBoard {
    Message { author: "Naomi" }
    Message { author: "Clancy" }
}

如果未将 messages 设为默认属性,则任何 Message 对象都必须显式分配给 messages 属性,如下所示

MessageBoard {
    messages: [
        Message { author: "Naomi" },
        Message { author: "Clancy" }
    ]
}

(顺便说一下,Item::data 属性是其默认属性。添加到此 data 属性的所有 Item 对象也将添加到 Item::children 列表中,因此使用默认属性可以在不显式分配给 children 属性的情况下声明项目的视觉子对象。)

此外,您可以声明一个“ParentProperty” Q_CLASSINFO() 来通知 QML 引擎在 QML 层次结构中哪个属性应表示父对象。例如,消息类型可能声明如下

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QObject* board READ board BINDABLE boardBindable)
    Q_PROPERTY(QString author READ author BINDABLE authorBindable)
    Q_CLASSINFO("ParentProperty", "board")
    QML_ELEMENT

public:
    Message(QObject *parent = nullptr) : QObject(parent) { m_board = parent; }

    QObject *board() const { return m_board.value(); }
    QBindable<QObject *> boardBindable() { return QBindable<QObject *>(&m_board); }

    QString author() const { return m_author.value(); }
    QBindable<QString> authorBindable() { return QBindable<QString>(&m_author); }

private:
    QProperty<QObject *> m_board;
    QProperty<QString> m_author;
};

定义父属性使得 qmllint 以及其他工具能更好地了解您的代码意图,并避免了在某些属性访问上的误报警告。

使用 Qt Quick 模块定义视觉项目

使用 Qt Quick 模块构建用户界面时,所有要视觉渲染的 QML 对象必须派生于 Item 类型,因为它是 Qt Quick 中所有视觉对象的基类型。此 Item 类型由 QQuickItem C++ 类实现,由 Qt Quick 模块提供。因此,当需要实现一个可以集成到基于 QML 的用户界面的 C++ 视觉类型时,应该继承此类。

有关更多信息,请参阅 QQuickItem 文档。此外,使用 C++ 编写 QML 扩展教程 展示了如何实现基于 QQuickItem 的视觉项目并在基于 Qt Quick 的用户界面中集成。

接收对象初始化通知

对于某些自定义的 QML 对象类型,可能有必要将特定数据延迟初始化直到对象被创建,并且所有属性都被设置。例如,这可能是在初始化成本较高或应该在所有属性值初始化后才执行初始化的情况下。

为了这些目的,Qt Qml 模块提供了需要继承的 QQmlParserStatus。它定义了在组件实例化期间调用的多个虚拟方法。要接收这些通知,应使 C++ 类继承 QQmlParserStatus 并使用 Q_INTERFACES() 宏通知 Qt 元系统。

例如

class MyQmlType : public QObject, public QQmlParserStatus
{
    Q_OBJECT
    Q_INTERFACES(QQmlParserStatus)
    QML_ELEMENT
public:
    virtual void componentComplete()
    {
        // Perform some initialization here now that the object is fully created
    }
};

© 2024 Qt公司有限公司。本文件中包含的文档贡献均为其各自所有者的版权。本提供的文档根据GNU自由文档许可证第1.3版许可,由自由软件开发基金会发布。Qt及其相关标志是Qt公司有限公司在芬兰及/或其他国家的商标。商标。所有其他商标均为其各自所有者的财产。