The Property System
Qt提供一个复杂的属性系统, 类似于某些编译器供应商提供的属性系统. 然而, 作为一个与编译器和平台无关的库, Qt不依赖非标准的编译器功能, 如 __property
或 [property]
. Qt的解决方案可以在Qt支持的所有平台与 任意 标准C++编译器一起使用. Qt属性系统基于 元对象系统, 也通过 信号和槽提供对象间通信.
属性声明要求
若要声明属性, 你必须继承QObject类, 并使用 Q_PROPERTY() 宏.
Q_PROPERTY(type name (READ getFunction [WRITE setFunction] | MEMBER memberName [(READ getFunction | WRITE setFunction)]) [RESET resetFunction] [NOTIFY notifySignal] [REVISION int] [DESIGNABLE bool] [SCRIPTABLE bool] [STORED bool] [USER bool] [CONSTANT] [FINAL])
下面给定一些典型的属性声明, 摘自 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;
属性行为与类数据成员类似, 但是它可以使用 元对象系统访问.
-
READ
如果未指定MEMBER
变量, 必须提供读函数. 函数用于读取属性值. 理想情况下, 这个函数用const修饰, 它返回属性类型或const引用. 如., QWidget::focus 是一个只读属性,READ
函数是 QWidget::hasFocus(). -
WRITE
函数用于设置属性值, 只接受一个参数, 返回值是void. 函数参数可以是属性类型的指针或引用. 如., QWidget::enabled 的WRITE
函数是 QWidget::setEnabled(). 只读属性不需要WRITE
函数. 如., QWidget::focus 没有WRITE
函数. -
MEMBER
没有READ
函数时, 必须关联变量. 这个标识表示成员变量支持读写, 无需创建READ
和WRITE
函数. 如果需要控制变量访问, 除MEMBER
变量外, 可以使用READ
或WRITE
函数 (但是两者不能同时存在). -
RESET
可选函数. 这个函数将属性设置为默认值. 如., QWidget::cursor 有READ
函数(QWidget::cursor())和WRITE
函数(QWidget::setCursor()), 它也有RESET
函数(QWidget::unsetCursor()), 由于没有调用 QWidget::setCursor()函数, 所以QWidget::cursor设置默认值.RESET
函数返回类型是void, 不带参数. -
NOTIFY
可选信号. 这个信号必须是类中现有信号. 这个值改变时会发出此信号.MEMBER
变量的NOTIFY
信号必须有0或1个参数, 这个参数与属性类型相同. 参数是属性的新值. 属性改变后才会发送NOTIFY
信号, 这样做是避免QML中不必要地重新评估绑定. 若没有显示设置MEMBER
属性, Qt会自动发送这个信号. -
REVISION
可选数字. 这个关键字表明属性和信号用于特定版本的API (常用于 QML). 若没有显示指定, 默认是0. -
DESIGNABLE
可选标识. 属性是否在GUI设计工具 (如., Qt Designer) 显示. 多数属性是DESIGNABLE
(默认 true). 你可以使用返回值是bool类型的函数, 替代true和false. -
SCRIPTABLE
可选标识. 属性是否关联脚本引擎(默认true). 你可以使用返回值是bool类型的函数, 替代true和false. -
STORED
可选标识. 属性是独立存在, 还是依赖其他值. 它还表明存储对象状态时, 是否必须保存属性值. 多数属性是STORED
(默认 true), 少数例外, 如., QWidget::minimumWidth() 的STORED
是 false, 这个函数的返回值是 QWidget::minimumSize()值( QSize )的分量. -
USER
可选标识. 属性对于用户是否可编辑. 通常, 每个类仅有一个USER
属性 (默认 false). 如., QAbstractButton::checked 是用户可编辑的属性. 注意: QItemDelegate 读写widget的USER
值. -
CONSTANT
可选标识. 属性值是否是常数. 对于给定对象实例, READ函数每次返回值必须相同. 对于对象的不同实例, 这个数值可能不同. 具有该标识的属性没有WRITE函数和NOTIFY信号. -
FINAL
可选标识. 属性是否可被派生类覆盖. 这个标识用于性能优化, 但是这个标识不会被moc强制遵守. 必须小心FINAL
属性. -
REQUIRED
可选标识. 指示这个属性必须被类用户赋值. moc不会强制遵守, 这个属性对于QML而言很有用. 在QML中, 用户必须对所有具有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
函数更好, 因为WRITE函数执行速度更快, 编译时更多检查, 但是这种方式要求你在编译时必须知道这个类. 调用QObject::setProperty()可以让你编译时不知道类信息. 你可以在运行时, 查询类的 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() 获取未知类的所有属性名称, 再调用 QObject::property() 获取属性值.
一个简单用例
假设我们有个类MyClass, 它派生自 QObject , 并声明 Q_OBJECT 宏. 我们想在类中声明一个属性priority并追踪其值. 这个属性的类型是MyClass类定义的枚举类型 Priority.
我们在类中声明Priority时, 使用 Q_PROPERTY() 宏. 其中, READ
函数是 priority
, WRITE
函数是 setPriority
. NOTIFY
信号是priorityChanged
. 枚举类型必须使用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 = 0); ~MyClass(); enum Priority { High, Low, VeryHigh, VeryLow }; Q_ENUM(Priority) void setPriority(Priority priority) { m_priority = priority; emit priorityChanged(priority); } Priority priority() const { return m_priority; } signals: void priorityChanged(Priority); private: Priority m_priority; };
READ
函数是const类型, 返回属性值. WRITE
函数传入一个参数, 并返回void类型. 元对象编译器强制这些要求.
对于MyClass实例指针或 QObject 指针, 我们有两种方式设置priority属性:
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_ENUM()类似的宏Q_FLAG(). 这个宏也注册一个枚举类型, 但是它将这个类型作为一组标识, 即值使用OR运算. 一个I/O类可能具有 Read
和 Write
枚举值, QObject::setProperty() 接受 Read | Write
. Q_FLAG() 用于注册此类枚举.
动态属性
QObject::setProperty() 也可用作运行时向类示例添加新属性. 调用QObject::setProperty(),传入参数名称和值, 若 QObject 调用QObject::setProperty(), 传入参数名称和值, 若QObject中存在给定名称的属性, 且属性类型和值兼容, 这个值存入属性, 返回true; 若这个值与属性类型不兼容, 不改变属性值, 返回false. 若QObject中不存在给定名称的属性(即., 未使用 Q_PROPERTY()声明), 新的属性和值自动加入 QObject中, 但是仍然返回false. 这意味着不能通过返回值(false)确定属性值是否修改成功, 除非你知道 QObject已存在该属性.
注意: 动态属性基于类实例添加, 即., 它们添加到 QObject, 而不是 QMetaObject. 调用QObject::setProperty(), 并传入名称和无效的 QVariant, 删除属性. QVariant 默认构造函数是一个无效的 QVariant.
调用 QObject::property()动态查询属性, 就像编译时使用 Q_PROPERTY()声明属性一样.
属性和自定义类型
属性可以使用自定义类型, 自定义类型使用 Q_DECLARE_METATYPE() 宏注册, 以便将其值存储到 QVariant 对象中. 通过这种方式, 自定义类型即可在类定义时, 作为 Q_PROPERTY() 宏中的静态属性, 又可在运行时, 创建动态属性.
向类添加附加信息
连接属性系统的是Q_CLASSINFO()宏, 这个宏支持在类的元对象上添加名值对数据, 如:
Q_CLASSINFO("Version", "3.0.0")
像其他元数据一样, 类信息可以在运行时通过元对象访问. 详见 QMetaObject::classInfo() for details.
参见 Meta-Object System, Signals and Slots, Q_DECLARE_METATYPE(), QMetaType, QVariant.