本页内容

从 C++ 定义 QML 类型

在通过 C++ 代码扩展 QML 时,可以将 C++ 类注册到 QML 类型系统中,从而使该类能够在 QML 代码中作为数据类型使用。 虽然如《将 C++ 类型的属性暴露给 QML》中所述,任何从QObject 派生的类的属性、方法和信号都可以从 QML 访问,但在向类型系统注册之前,此类不能在 QML 中用作数据类型。 此外,注册还可以提供其他功能,例如允许在 QML 中将该类用作可实例化的QML 对象类型,或者允许从 QML 导入并使用该类的单例实例。

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

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

注意:所有声明 QML 类型的头文件都必须能够从项目的包含路径中直接访问,无需任何前缀。

有关 C++ 以及各种 QML 集成方法的更多信息,请参阅“C++ 与 QML 集成概述”页面。

将 C++ 类型注册到 QML 类型系统

QObject 的派生类可以注册到 QML 类型系统中,从而使该类型能够作为数据类型在 QML 代码中使用。

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

注册不可实例化的类型也会以这种方式将该类注册为数据类型,但该类型无法在 QML 中作为 QML 对象类型进行实例化。例如,当某种类型包含应向 QML 公开的枚举,但该类型本身不应可实例化时,此方法非常有用。

有关如何选择正确方法将 C++ 类型暴露给 QML 的快速指南,请参阅《选择 C++ 与 QML 之间的正确集成方法》。

先决条件

下面提到的所有宏均可从 QtQmlIntegration 模块中的qqmlintegration.h 头文件中获取。

您需要在使用这些宏的文件中添加以下代码,以便使这些宏可用:

#include <QtQmlIntegration/qqmlintegration.h>

如果您已经链接了QtQml 模块,则可以使用qqmlregistration.h 头文件(该文件会包含qqmlintegration.h ),具体如下:

#include <QtQml/qqmlregistration.h>

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

注册可实例化对象类型

任何从 `QObject` 派生的 C++ 类均可注册为QML 对象类型的定义。一旦类在 QML 类型系统中注册,即可像其他任何对象类型一样,在 QML 代码中进行声明和实例化。 创建后,即可从 QML 中操作该类实例;正如《将 C++ 类型的属性暴露给 QML》一节所述,任何继承自 `QObject` 的类的属性、方法和信号均可从 QML 代码中访问。

要将一个从 `QObject` 派生的类注册为可实例化的 QML 对象类型,请在类声明中添加 `QML_ELEMENT ` 或 `QML_NAMED_ELEMENT(<name>) `。您还需要对构建系统进行相应调整。对于 qmake,请在项目文件中添加 `CONFIG += qmltypes`、`QML_IMPORT_NAME` 以及 `QML_IMPORT_MAJOR_VERSION `。 对于 CMake,包含该类的文件应作为目标的一部分,并通过qt_add_qml_module() 进行设置。这将把该类注册到指定主版本下的类型命名空间中,并使用类名或显式指定的名称作为 QML 类型名。 次版本号将根据属性、方法或信号所关联的修订号推导得出。默认次版本号为0 。您可以在类声明中添加QML_ADDED_IN_VERSION() 宏,以显式限制该类型仅在特定次版本中可用。客户端可以导入该命名空间的合适版本来使用该类型。

例如,假设有一个名为Message 的类,其中包含author 和creationDate 属性:

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_TYPE 或QML_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(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 的枚举,其取值为A 、B 和C 。然后,您可以在QML中编写以下代码:

someProperty: Person.A

与此同时,您仍然可以像以前一样使用名为person (小写)的值类型。

注册不可实例化的类型

有时,QObject 的派生类可能需要向 QML 类型系统注册,但并非作为可实例化类型。例如,当一个 C++ 类:

  • 是一个不应可实例化的接口类型
  • 作为基类类型,但无需向 QML 公开
  • 声明了一些应可从 QML 访问的枚举,但除此之外不应可实例化
  • 是一种应通过单例实例提供给 QML 的类型,且不应从 QML 进行实例化

该 Qt Qml 模块提供了若干宏,用于注册不可实例化的类型:

  • QML_ANONYMOUS 用于注册一个无法实例化且无法从 QML 引用的 C++ 类型。这使引擎能够对任何可从 QML 实例化的派生类型进行强制转换。
  • QML_INTERFACE 用于注册现有的 Qt 接口类型。该类型无法从 Qml 进行实例化,且无法用其声明 Qml 属性。不过,在 Qml 中使用此类 C++ 属性时,系统会执行预期的接口强制转换。
  • QML_UNCREATABLE(reason) 结合QML_ELEMENT 或QML_NAMED_ELEMENT 可注册一个命名 C++ 类型,该类型不可实例化,但应能被 QML 类型系统识别为一种类型。当某类型的枚举或附加属性应可从 QML 访问,但类型本身不应可实例化时,此方法非常有用。 该参数应为一条错误消息,当检测到尝试创建该类型的实例时,将触发该错误消息。
  • QML_SINGLETON 与 `QML_ELEMENT ` 或 `QML_NAMED_ELEMENT ` 结合使用时,可注册一种可从 QML 导入的单例类型,具体如下所述。

请注意,所有在 QML 类型系统中注册的 C++ 类型都必须继承自 `QObject`,即使它们不可实例化也是如此。

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

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

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

QObject 单例类型与其他QObject 或实例化类型的交互方式类似,唯一不同之处在于:仅存在一个(由引擎构建并拥有的)实例,且必须通过类型名称而非ID来引用它。QObject 单例类型的Q_PROPERTY属性可以被绑定,且QObject 模块API中的Q_INVOKABLE 函数可在信号处理程序表达式中使用。这使得单例类型成为实现样式或主题的理想方式,同时它们也可替代“.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 修饰符将Q_PROPERTY 声明为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 宏进行版本标记:

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

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

以这种方式指定的修订号会被自动解释为项目文件中给定主版本的次版本。在此情况下,只有在导入MyTypes 1.1版或更高版本时,root 才可用。导入MyTypes 1.0版的情况则不受影响。

出于同样的原因,在后续版本中引入的新类型应标记为QML_ADDED_IN_VERSION 宏。

该语言特性允许在不破坏现有应用程序的情况下进行行为变更。因此,QML 模块作者应始终记得记录次版本之间的变更内容,而 QML 模块用户在部署更新的导入语句之前,应检查其应用程序是否仍能正常运行。

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

注意: QML 引擎不支持分组和附加属性对象的属性或信号的修订版本。

注册扩展对象

在将现有类和技术集成到 QML 时,通常需要对 API 进行微调,以更好地适应声明式环境。尽管直接修改原始类通常能获得最佳效果,但如果无法直接修改,或者因其他因素导致修改过程较为复杂,则可通过扩展对象在无需直接修改的情况下,对您所控制的类型进行有限的扩展。 不支持扩展 Qt 自身的类型。

扩展对象为现有类型添加了额外的属性。扩展类型定义允许程序员在注册类时提供一个额外的类型,称为扩展类型。当在 QML 中使用时,其成员会与原始目标类透明地合并。例如:

QLineEdit {
    leftMargin: 20
}

leftMargin 属性是在不修改源代码的情况下,为现有 C++ 类型QLineEdit 添加的新属性。

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

您还可以使用QML_EXTENDED_NAMESPACE(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 ...
};

通过这段代码,您将获得一个 QML 类型,该类型既包含 Immutable3rdParty 的方法和属性,又包含 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.enabled 和Keys.onReturnPressed 的值。这使得Item 对象能够将其作为自身现有属性的扩展来访问这些额外属性。

实现附加对象的步骤

在考虑上述示例时,涉及以下几个方面:

  • 有一个匿名附加对象类型的实例,它具有enabled 属性以及returnPressed 信号,该实例已被附加到Item 对象上,以便该对象能够访问和设置这些属性。
  • Item 对象是被附加对象,附加对象类型的实例已被附加到该对象上。
  • Keys 是附加类型,它为被附加对象提供了一个名为“Keys”的限定符,通过该限定符,被附加对象可以访问附加对象类型的属性。

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

提供关联对象的机制可通过在 C++ 中为关联对象类型和关联类型提供相应的类来实现。对于关联对象类型,需提供一个从QObject 派生的类,该类定义了要向被关联对象开放的属性。对于关联类型,需提供一个从QObject 派生的类,该类:

  • 实现一个具有以下签名的静态函数 qmlAttachedProperties():
    static <AttachedPropertiesType> *qmlAttachedProperties(QObject *object);

    该方法应返回一个被附加对象类型的实例。

    QML引擎调用此方法,旨在将“attached object”类型的实例附加到由object 参数指定的被附加对象上。虽然并非严格要求,但通常建议该方法的实现将返回的实例作为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 上并不合理——因为这些属性与消息板的上下文更为相关——因此可以将其作为Message 对象上的附加属性来实现,并通过“MessageBoard”限定符提供。根据前面描述的概念,此处的参与方包括:

  • 一个匿名附加对象类型的实例,该实例提供published 信号和expired 属性。该类型由下文中的MessageBoardAttachedType 实现
  • 一个 `Message ` 对象,它将作为被附加对象
  • MessageBoard 类型,即作为“attaching”类型的类型,用于让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` 必须声明一个 `qmlAttachedProperties() ` 方法,该方法返回由 `MessageBoardAttachedType` 实现的关联对象类型的实例。此外,必须通过 `QML_ATTACHED()` 宏将 `MessageBoard ` 声明为关联类型:

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 可以被子类化,以将父对象的关联属性传播到其子对象,类似于font 和palette 的传播机制。它支持通过items 、popups 和windows 进行传播。

属性修饰符类型

属性修饰符类型是一种特殊的 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++ 实现属性值源。当在 QML 中使用 `<PropertyValueSource> on <property> ` 语法将属性值源应用于某个属性时,引擎会向其提供该属性的引用,以便更新属性值。

例如,假设有一个RandomNumberGenerator 类要作为属性值源提供,当将其应用于QML属性时,它将每500毫秒将该属性的值更新为一个不同的随机数。此外,还可以为该随机数生成器提供一个maxValue参数。该类的实现如下:

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` 派生类型,均可选择性地为该类型指定一个默认属性。默认属性是指当对象的子节点未被分配给任何特定属性时,系统会自动将其赋值给该属性的属性。

可以通过为类调用Q_CLASSINFO()宏并指定特定的“DefaultProperty”值来设置默认属性。例如,下面的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 层次结构中表示父对象。例如,Message 类型可以声明如下:

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 模块提供。因此,当需要在 C++ 中实现一种可集成到基于 QML 的用户界面的视觉类型时,应继承该类。

有关更多信息,请参阅QQuickItem 文档。此外,教程《使用 C++ 编写 QML 扩展》演示了如何在 C++ 中实现基于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
    }
};

另请参阅《 QML Type Registration Macros 》 和《概述 - QML 与 C++ 集成》。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.