从 C++ 定义 QML 类型
当用 C++ 代码扩展 QML 时,C++ 类可在 QML 类型系统注册,使类能在 QML 代码中用作数据类型。如Exposing Attributes of C++ Types to QML 所述,任何QObject 衍生类的属性、方法和信号都可从 QML 访问,但这样的类在注册到类型系统之前,不能用作 QML 的数据类型。此外,注册还能提供其他功能,如允许从 QML 把类用作可实例化的QML 对象类型,或允许从 QML 导入和使用类的单例。
此外,该 Qt Qml模块提供了实现 QML 特定功能的机制,如 C++ 中的附加属性和默认属性。
(请注意,本文档中涉及的许多重要概念已在《用 C++ 编写 QML 扩展》(Writing QML Extensions with C++)教程中演示)。
注意:所有声明 QML 类型的头文件(headers)都需要从项目的 include 路径访问,不需要任何前缀。
有关 C++ 和不同 QML 集成方法的更多信息,请参阅C++ 和 QML 集成概述页面。
用 QML 类型系统注册 C++ 类型
QObject 衍生的类可在 QML 类型系统注册,使该类型在 QML 代码中用作数据类型。
引擎允许注册可实例化和不可实例化的类型。注册可实例化类型,可让 C++ 类用作 QML 对象类型的定义,让它在 QML 代码的对象声明中使用,以创建该类型的对象。注册还为引擎提供了额外的类型元数据(type metadata),使该类型(以及该类声明的任何枚举)可用作 QML 和 C++ 之间交换的属性值、方法参数和返回值以及信号参数的数据类型。
注册不可实例化的类型(non-instantiable type)也能以这种方式把类注册为数据类型,但该类型不能从 QML 实例化为 QML 对象类型。例如,如果一个类型有枚举(enums),应暴露给 QML,但类型本身不能实例化,这就很有用。
有关选择将 C++ 类型暴露给 QML 的正确方法的快速指南,请参阅《选择 C++ 和 QML 之间的正确集成方法》(Choosing the Correct Integration Method Between C++ and QML)。
前提条件
下面提到的所有宏都可从 QtQmlIntegration 模块的qqmlintegration.h头文件中获取。
您需要在使用这些宏的文件中添加以下代码,以使这些宏可用:
#include <QtQmlIntegration/qqmlintegration.h>
如果你已经链接到QtQml 模块,你可以使用 qqmlregistration.h 头文件,它将包含qqmlintegration.h,如下所示:
#include <QtQml/qqmlregistration.h>
此外,你的类声明必须包含在头文件中,通过你的项目的包含路径才能到达。声明用于在编译时生成注册代码,而注册代码需要包含包含声明的头文件。
注册可实例化的对象类型
任何QObject 衍生的C++ 类都可以注册为QML 对象类型的定义。一旦类在 QML 类型系统中注册,类就可以像 QML 代码中的其它对象类型一样被声明和实例化。如Exposing Attributes of C++ Types to 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()
宏,明确限制该类型只能在特定次版本中使用。客户端可以导入命名空间的适当版本,以便使用该类型。
例如,假设有一个带有author
和creationDate
属性的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 代码中用作属性类型。正如Exposing Attributes of C++ Types to QML所解释的,QML 代码可以访问任何值类型的属性和方法。
与对象类型不同,值类型需要小写名称。注册它们的首选方法是使用QML_VALUE_TYPE 或QML_ANONYMOUS 宏。QML_ELEMENT 并不等同,因为你的 C++ 类通常会使用大写名称。除此之外,注册与对象类型的注册非常相似。
例如,假设您要注册一个值类型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 注册不可实例化的 C++ 类型,不能从 QML 引用。这样,引擎就能强制从 QML 中实例化任何继承类型。
- QML_INTERFACE 注册现有的 Qt 接口类型。该类型不能从 QML 实例化,也不能用它声明 QML 属性。不过,从 QML 使用该类型的 C++ 属性将完成预期的接口转换。
- QML_UNCREATABLE(reason)结合QML_ELEMENT 或QML_NAMED_ELEMENT 注册一个命名的 C++ 类型,它不可实例化,但在 QML 类型系统中应可识别为一个类型。如果 QML 可以访问一个类型的枚举或附加属性,但该类型本身不可实例化,这就很有用。如果检测到有人试图创建该类型的实例,参数应该是一条错误信息。
- QML_SINGLETON 与 或 结合使用,将注册一个可从 QML 导入的单例类型,如下所述。QML_ELEMENT QML_NAMED_ELEMENT
请注意,所有在 QML 类型系统注册的 C++ 类型都必须是QObject-derived(派生)的,即使它们是不可实例化的。
用单例类型注册单例对象
单例类型能让属性、信号和方法在命名空间中公开,而不需要客户端手动实例化对象实例。QObject 单例类型尤其是提供功能或全局属性值的高效便捷方法。
请注意,单例类型没有相关的QQmlContext ,因为它们在引擎的所有上下文中共享。QObject ,单例类型实例由QQmlEngine 构建和拥有,并将在引擎被销毁时销毁。
QObject 单例类型的交互方式与任何其他QObject 或实例化类型的交互方式类似,但只有一个(由引擎构建和拥有的)实例存在,而且必须通过类型名称而不是 id 来引用它。可以绑定QObject 单例类型的 Q_PROPERTY,也可以在信号处理表达式中使用QObject 模块 API 的Q_INVOKABLE 函数。这使得单例类型成为实现风格化或主题化的理想方式,它们还可以用来代替".pragma 库 "脚本导入,以存储全局状态或提供全局功能。
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 中的单例》(Singletons in QML)。
注意: QML 中注册类型的 Enum 值应该以大写字母开头。
最终属性
使用FINAL
modifier 到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's、信号和槽等方法也可以使用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 通常需要调整以更好地适应声明式环境。虽然直接修改原始类通常能获得最佳效果,但如果这不可能或因其他问题而变得复杂,扩展对象就能在不直接修改的情况下实现有限的扩展。
扩展对象为现有类型添加了额外的属性。扩展类型定义允许程序员在注册类时提供一个额外的类型,即扩展类型。在 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 特质(如:单例、扩展)。
定义 QML 特定类型和属性
提供附加属性
在 QML 语言语法中,有附加属性(attached properties)和附加信号处理器(attached signal handlers)的概念,它们是附加到对象的额外属性。本质上,这些属性是由附加类型实现和提供的,这些属性可以附加到另一种类型的对象上。这与由对象类型本身(或对象的继承类型)提供的普通对象属性截然不同。
例如,下面的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 键 "是附加类型,它为附加 对象提供了一个命名限定符 "键",通过它可以访问附加对象类型的属性。
QML 引擎处理这段代码时,会创建附加对象类型的一个实例,并将该实例附加到Item 对象,从而使它能访问该实例的enabled
和returnPressed
属性。
通过为附加对象类型和附加类型提供类,可以用 C++ 实现提供附加对象的机制。对于附加对象类型,提供一个QObject 衍生类,该类定义了附加对象可访问的属性。对于附加类型,提供一个QObject 衍生类,用于
- 实现具有以下签名的静态 qmlAttachedProperties() 属性:
static <AttachedPropertiesType> *qmlAttachedProperties(QObject *object);
该方法应返回附加对象类型的实例。
QML 引擎调用此方法,将 attached 对象类型的实例附加到
object
参数指定的attachee上。为防止内存泄漏,该方法的实现通常将返回的实例作为object
的父类,但这并非严格要求。引擎对每个附件对象实例最多调用一次该方法,因为引擎会缓存返回的实例指针,以便后续访问附件属性时使用。因此,在附件
object
销毁之前,附件对象可能不会被删除。 - 通过在类声明中添加QML_ATTACHED(attached) 宏,可以将 "attache "声明为附加类型。参数是附加对象类型的名称
实现附加对象:示例
例如,以前面例子中描述的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
对象的附加属性来实现,并通过 "留言板 "限定符来提供。根据前面描述的概念,这里涉及的各方是
- 匿名附加对象类型的实例,该实例提供
published
信号和expired
属性。该类型由以下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
必须声明一个qmlAttachedProperties()
方法,返回由 MessageBoardAttachedType 实现的附加对象类型的实例。此外,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 可以被子类化,以便从父对象向子对象传播附加属性,类似于 和 传播。它支持通过 、 和 传播。font palette items popups windows
属性修饰符类型
属性修改器类型是一种特殊的 QML 对象类型。属性修改器类型实例会影响它所应用的(QML 对象实例的)属性。有两种不同的属性修改器类型:
- 属性值写入拦截器
- 属性值源
属性值写入拦截器可用于过滤或修改写入属性的值。目前,唯一支持的属性值写入拦截器是QtQuick
import 提供的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 对象类型指定默认属性和父属性
任何QObject 衍生的类型,只要注册为可实例化的 QML 对象类型,就可以选择性地为该类型指定默认属性。默认属性是对象的子属性,如果它们没有被分配给任何特定属性,就会自动分配给它们。
默认属性可通过调用具有特定 "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 层次结构中的父对象。例如,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++ 类实现。因此,当需要用 C++ 实现可视化类型并将其集成到基于 QML 的用户界面中时,应该子类化该类。
更多信息,请参阅QQuickItem 文档。此外,Writing QML Extensions with C++tutorial 演示了如何用 C++ 实现基于QQuickItem 的可视化项目,并将其集成到基于Qt Quick 的用户界面中。
接收对象初始化通知
对于某些自定义 QML 对象类型,推迟初始化特定数据,直到对象被创建并设置了它的所有属性,可能是有益的。例如,如果初始化代价高昂,或如果初始化要等到所有属性值都初始化后才能执行,就会出现这种情况。
该 Qt Qml模块提供了用于这些目的的子类库QQmlParserStatus 。该模块定义了许多虚拟方法,可在组件实例化的不同阶段调用。要接收这些通知,C++ 类应继承QQmlParserStatus ,同时使用Q_INTERFACES() 宏通知 Qt XML 元系统。
例如
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 } };
© 2025 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.