属性系统
Qt 提供了一个复杂的属性系统,与某些编译器供应商提供的系统类似。不过,作为一个独立于编译器和平台的库,Qt 并不依赖于非标准编译器功能,如__property
或[property]
。Qt 解决方案可在 Qt 支持的每个平台上与任何标准 C++ 编译器配合使用。它基于元对象系统(Meta-Object System),该系统还通过信号和插槽提供对象间通信。
声明属性的要求
要声明属性,请在继承QObject 的类中使用Q_PROPERTY() 宏。
Q_PROPERTY(type name (READ getFunction [WRITE setFunction] | MEMBER memberName [(READ getFunction | WRITE setFunction)]) [RESET resetFunction] [NOTIFY notifySignal] [REVISION int | REVISION(int[, int])] [DESIGNABLE bool] [SCRIPTABLE bool] [STORED bool] [USER bool] [BINDABLE bindableProperty] [CONSTANT] [FINAL] [REQUIRED])
下面是一些典型的属性声明示例,摘自类QWidget 。
Q_PROPERTY(bool focus READ hasFocus) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)
下面的示例展示了如何使用MEMBER
关键字将成员变量导出为 Qt XML 属性。请注意,必须指定NOTIFY
信号才能允许 QML 属性绑定。
Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged) Q_PROPERTY(qreal spacing MEMBER m_spacing NOTIFY spacingChanged) Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged) ... signals: void colorChanged(); void spacingChanged(); void textChanged(const QString &newText); private: QColor m_color; qreal m_spacing; QString m_text;
属性的行为类似于类的数据成员,但它具有通过元对象系统(Meta-Object System)访问的附加功能。
- 如果没有指定
MEMBER
变量,则需要READ
访问函数。它用于读取属性值。理想情况下,为此目的使用一个常量函数,它必须返回属性的类型或该类型的常量引用。例如,QWidget::focus 是一个只读属性,使用READ
函数QWidget::hasFocus() 。如果指定了BINDABLE
,则可以编写READ default
,以便从BINDABLE
生成READ
访问函数。 WRITE
访问函数是可选的。它用于设置属性值。它必须返回 void,并且必须接受一个参数,该参数可以是属性类型,也可以是该类型的指针或引用。例如,QWidget::enabled 有WRITE
函数QWidget::setEnabled()。只读属性不需要WRITE
函数。例如,QWidget::focus 没有WRITE
函数。如果同时指定BINDABLE
和WRITE default
,将从BINDABLE
生成WRITE
访问器。生成的WRITE
访问器不会显式地发出用NOTIFY
声明的任何信号。应将信号注册为BINDABLE
的更改处理程序,例如使用Q_OBJECT_BINDABLE_PROPERTY 。- 如果没有指定
READ
访问函数,则需要与MEMBER
变量关联。这样,无需创建READ
和WRITE
访问函数即可读写给定的成员变量。如果需要控制变量的访问,除了MEMBER
变量关联之外,还可以使用READ
或WRITE
访问函数(但不能同时使用)。 RESET
函数是可选的。例如,QWidget::cursor 具有典型的READ
和WRITE
函数、QWidget::cursor() 和QWidget::setCursor() 以及RESET
函数QWidget::unsetCursor(),因为不调用QWidget::setCursor() 就意味着重置为上下文特定的游标。RESET
函数必须返回 void,且不带参数。NOTIFY
信号是可选的。MEMBER
如果已定义,则应指定该类中的一个现有信号,每当属性值发生变化时,该信号就会发出。NOTIFY
变量的信号必须接受 0 个或 1 个参数,参数必须与属性类型相同。参数将接收属性的新值。NOTIFY
信号只应在属性确实发生变化时发出,以避免绑定在 QML 中不必要地重新评估。通过 Qt XML API(QObject::setProperty,QMetaProperty 等)更改属性时会自动发出该信号,但直接更改 MEMBER 时不会发出该信号。REVISION
数字或REVISION()
宏是可选的。如果包含,它将定义在特定版本的 API(通常用于 QML)中使用的属性及其通知信号。如果不包含,默认值为 0。DESIGNABLE
属性表示该属性是否应在 GUI 设计工具(如Qt Widgets Designer)的属性编辑器中可见。大多数属性都是DESIGNABLE
(默认为 true)。有效值为 true 和 false。SCRIPTABLE
属性表示脚本引擎是否可以访问该属性(默认为 true)。有效值为 true 和 false。STORED
属性表示该属性是独立存在还是依赖于其他值。它还表示在存储对象状态时是否必须保存属性值。大多数属性都是STORED
(默认为 true),但例如QWidget::minimumWidth() 的STORED
是 false,因为它的值只是取自属性QWidget::minimumSize() 的宽度分量,而 () 是QSize 。USER
属性表示该属性是被指定为面向用户的属性还是类的用户可编辑属性。通常,每个类只有一个USER
属性(默认为 false)。例如,QAbstractButton::checked 是(可选中)按钮的用户可编辑属性。请注意,QItemDelegate 可获取和设置部件的USER
属性。BINDABLE bindableProperty
属性表示该属性支持绑定,可以通过元对象系统 (QMetaProperty) 设置和检查该属性的绑定。bindableProperty
命名了QBindable<T> 类型的类成员,其中 T 是属性类型。该属性在 Qt 6.0 中引入。CONSTANT
属性的存在表明属性值是常量。对于给定的对象实例,常量属性的 READ 方法每次调用都必须返回相同的值。不同对象实例的常量值可能不同。常量属性不能有 WRITE 方法或 NOTIFY 信号。FINAL
属性的存在表明该属性不会被派生类覆盖。这在某些情况下可用于优化性能,但 moc 不会强制执行。必须注意不要覆盖FINAL
属性。REQUIRED
属性的存在表明,该属性应由类的用户设置。moc 并不强制执行该属性,它主要适用于暴露于 QML 的类。在 QML 中,除非所有 REQUIRED 属性都已设置,否则带有 REQUIRED 属性的类不能被实例化。
READ
、WRITE
和RESET
函数可以继承。它们也可以是虚拟的。在使用多重继承的类中继承时,它们必须来自第一个继承的类。
属性类型可以是QVariant 支持的任何类型,也可以是用户定义的类型。在本例中,类QDate 被视为用户定义的类型。
Q_PROPERTY(QDate date READ getDate WRITE setDate)
由于QDate 是用户定义的,因此必须在属性声明中包含<QDate>
头文件。
由于历史原因,作为属性类型的QMap 和QList 是QVariantMap 和QVariantList 的同义词。
使用元对象系统读写属性
使用通用函数QObject::property() 和QObject::setProperty() 可以读写属性,除了属性名称外,无需知道属性所属类的任何信息。在下面的代码片段中,调用QAbstractButton::setDown() 和调用QObject::setProperty() 都设置了属性 "down"。
QPushButton *button = new QPushButton; QObject *object = button; button->setDown(true); object->setProperty("down", true);
通过WRITE
访问器访问属性是这两种方法中较好的一种,因为它速度更快,并能在编译时提供更好的诊断,但通过这种方法设置属性需要在编译时了解该类。通过名称访问属性可以访问编译时不知道的类。您可以在运行时通过查询类的QObject 、QMetaObject 和QMetaProperties 来发现该类的属性。
QObject *object = ... const QMetaObject *metaobject = object->metaObject(); int count = metaobject->propertyCount(); for (int i=0; i<count; ++i) { QMetaProperty metaproperty = metaobject->property(i); const char *name = metaproperty.name(); QVariant value = object->property(name); ... }
在上面的代码段中,QMetaObject::property() 用于获取某个未知类中定义的每个属性的metadata 。从元数据中获取属性名称并传递给QObject::property() 以获取当前object 中该属性的value 。
一个简单的例子
假设我们有一个从QObject 派生的类MyClass
,该类使用了Q_OBJECT 宏。我们想在MyClass
中声明一个属性来跟踪优先级值。该属性的名称是priority
,其类型是一个名为Priority
的枚举类型,定义在MyClass
中。
我们在类的私有部分使用Q_PROPERTY() 宏声明该属性。所需的READ
函数名为priority
,我们还包括一个名为setPriority
的WRITE
函数。枚举类型必须使用Q_ENUM() 宏在元对象系统中注册。注册枚举类型后,在调用QObject::setProperty() 时就可以使用枚举器名称。我们还必须为READ
和WRITE
函数提供自己的声明。因此,MyClass
的声明可能如下所示:
class MyClass : public QObject { Q_OBJECT Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged) public: MyClass(QObject *parent = nullptr); ~MyClass(); enum Priority { High, Low, VeryHigh, VeryLow }; Q_ENUM(Priority) void setPriority(Priority priority) { if (m_priority == priority) return; m_priority = priority; emit priorityChanged(priority); } Priority priority() const { return m_priority; } signals: void priorityChanged(Priority); private: Priority m_priority; };
READ
函数是 const,返回属性类型。WRITE
函数返回 void,并且只有一个属性类型的参数。元对象编译器会强制执行这些要求。WRITE
函数中的相等性检查虽然不是强制性的,但却是一种很好的做法,因为如果没有任何变化,就没有必要在其他地方进行通知和潜在的重新评估。
给定一个指向MyClass
实例的指针或一个指向作为MyClass
实例的QObject 的指针,我们有两种方法来设置其优先级属性:
MyClass *myinstance = new MyClass; QObject *object = myinstance; myinstance->setPriority(MyClass::VeryHigh); object->setProperty("priority", "VeryHigh");
在示例中,作为属性类型的枚举类型在MyClass
中声明,并使用Q_ENUM() 宏在元对象系统中注册。这样,枚举值就可以作为字符串在调用setProperty() 时使用。如果在其他类中声明了枚举类型,则需要使用其完全限定名称(即 OtherClass::Priority),其他类也必须继承QObject 并使用Q_ENUM() 宏注册枚举类型。
类似的宏Q_FLAG() 也可用。与Q_ENUM() 一样,它也注册了一个枚举类型,但它将该类型标记为一组标志,即可以一起 OR 的值。一个 I/O 类可能有枚举值Read
和Write
,然后QObject::setProperty() 可以接受Read | Write
。应该使用Q_FLAG() 来注册这种枚举类型。
动态属性
QObject::setProperty() 也可用于在运行时为类的实例添加新属性。当使用名称和值调用该函数时,如果QObject 中存在具有给定名称的属性,且给定值与属性类型兼容,则该值将存储在属性中,并返回 true。如果值与属性类型不兼容,则不更改属性,并返回 false。但是,如果在QObject 中不存在具有给定名称的属性(即没有用Q_PROPERTY() 声明),则会自动在QObject 中添加一个具有给定名称和值的新属性,但仍会返回 false。这意味着,除非事先知道QObject 中已存在该属性,否则无法使用 false 返回值来确定特定属性是否已被设置。
请注意,动态属性是按实例添加的,也就是说,它们是添加到QObject ,而不是QMetaObject 。通过向QObject::setProperty() 传递属性名称和无效的QVariant 值,可以从实例中移除属性。QVariant 的默认构造函数会构造一个无效的QVariant 。
动态属性可通过QObject::property() 进行查询,就像编译时通过Q_PROPERTY() 声明的属性一样。
属性和自定义类型
属性使用的自定义类型需要使用Q_DECLARE_METATYPE() 宏注册,这样它们的值才能存储在QVariant 对象中。这样,自定义类型既可用于在类定义中使用Q_PROPERTY() 宏声明的静态属性,也可用于在运行时创建的动态属性。
为类添加附加信息
与属性系统相连的是一个附加宏Q_CLASSINFO() ,可用于将附加的名 -值对附加到类的元对象上。例如,在QML 对象类型(Object Types)的上下文中,它可用于将一个属性标记为默认属性:
Q_CLASSINFO("DefaultProperty", "content")
与其他元数据一样,类信息可在运行时通过元对象访问;详情请查看QMetaObject::classInfo() 。
使用可绑定属性
有三种不同的类型可用于实现可绑定属性:
第一个是可绑定属性的通用类。后两种只能在QObject 中使用。
有关包括示例在内的更多信息,请参阅上文提及的类以及有关实现和使用可绑定属性的一般提示。
另请参阅 《元对象系统》(Meta-Object System)、《信号与插槽》(Signals and Slots)、《Q_DECLARE_METATYPE( )》、《QMetaType 》、《QVariant 》、《Qt 可绑定属性》(Qt Bindable Properties)和《从 C++ 定义 QML 类型》(Defining QML Types from C++)。
© 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.