属性系统
Qt 提供了一个功能完善的属性系统,类似于某些编译器供应商所提供的系统。 然而,作为一种与编译器和平台无关的库,Qt 并不依赖于诸如__property 或[property] 之类的非标准编译器特性。Qt 的解决方案可在 Qt 支持的任何平台上与任何标准C++ 编译器配合使用。它基于元对象系统,该系统还通过信号和槽实现对象间的通信。
声明属性的要求
要声明属性,请在继承自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]
[VIRTUAL]
[OVERRIDE]
[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属性。请注意,必须指定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;属性行为类似于类的数据成员,但具有可通过元对象系统访问的额外功能。
- 如果未指定
MEMBER变量,则必须提供READ访问器函数。该函数用于读取属性值。 理想情况下,应使用 const 函数来实现此功能,且该函数必须返回属性的类型或该类型的 const 引用。例如,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信号必须接受零个或一个参数,且该参数的类型必须与属性相同。该参数将接收属性的新值。NOTIFY信号仅应在属性确实发生更改时才触发,以避免例如在QML中不必要地重新评估绑定。当通过Qt 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()的width组件,而该组件属于QSize 。USER属性用于指示该属性是否被指定为该类的面向用户或可编辑属性。通常,每个类只有一个USER属性(默认值为false)。例如,QAbstractButton::checked 是(可选中)按钮的可编辑属性。请注意,QItemDelegate 用于获取和设置控件的USER属性。BINDABLE bindableProperty属性表示该属性支持绑定,并且可以通过元对象系统(QMetaProperty )设置和检查对此属性的绑定。bindableProperty指定了一个类型为QBindable<T> 的类成员,其中 T 是属性的类型。该属性在 Qt 6.0 中引入。CONSTANT属性的存在表明该属性值是常量。对于给定的对象实例,常量属性的READ方法每次被调用时都必须返回相同的值。该常量值对于对象的不同实例可能不同。常量属性不能具有WRITE方法或NOTIFY信号。FINALVIRTUAL、 修饰符与它们在 C++ 和OVERRIDEQML 中的对应修饰符语义一致,允许在元对象层面上显式地重写属性。注意:目前, 这些修饰符不会被 moc 强制执行。它们在语法上被识别,主要用于 QML Runtime 强制执行和工具诊断。未来版本可能会引入更严格的编译时验证,并对跨模块的无效重写发出警告。
注意:若 要更改属性的访问行为,请使用 C++ 提供的多态性。
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 = new QObject;
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 。
一个简单示例
假设我们有一个类MyClass ,它继承自QObject 并使用了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,且仅有一个属性类型的参数。Meta-Object Compiler会强制执行这些要求。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() 一样,用于注册枚举类型,但会将该类型标记为一组标志,即可以进行“或”运算的值。 一个 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 中使用。
有关更多信息(包括示例),请参阅上述类以及关于实现和使用可绑定属性的通用提示。
另请参阅 元对象系统、信号与槽、Q_DECLARE_METATYPE()、QMetaType 、QVariant 、Qt 可绑定属性以及从 C++ 定义 QML 类型。
© 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.