感谢MaxValue,hennychen 对本文的翻译,同时非常感谢Cxt_programmer在百忙中抽出时间对翻译初稿的认真校验。才使本文与读者尽快见面。由于书稿内容多,我们的知识有限,尽管我们进行了细心的检查,但是还是会存在错误,这里恳请广大读者批评指正,并发送邮件至BeyondVincent@devdiv.com,在此我们表示衷心的感谢。
注:本文原文地址:The Property System。
第一章 属性系统
Qt提供了一个成熟的属性系统,它和某些编译器厂商提供的属性系统相似。但是,作为一个编译器及平台独立的程序库,Qt并不依赖非标准的编译器特性,如__property或[property]。Qt对属性系统的解决方案可以在Qt支持的每一个平台上的任何标准编译器上工作,它是基于元对象系统(Meta-Object System)的。对象间通信用的信号和槽机制也是基于元对象系统的。
第二章 定义属性的要求
在继承自QObject的类中使用Q_PROPERTY()宏定义属性。
Q_PROPERTY(type name
READ getFunction
[WRITE setFunction]
[RESET resetFunction]
[NOTIFY notifySignal]
[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)
虽然属性的行为与类数据成员相似,但是,它具有额外的特性,这些特性可以通过元对象系统访问。
n READ 存取函数是必须的。它用于读取属性的值。原则上,这里使用const函数,并且它必须返回属性的类型、指针或者引用。例如,QWidget::focus 是与 READ函数QWidget::hasFocus()对应的只读属性。
n WRITE 存取函数是可选的。它用于设置属性的值。它必须返回void并且带一个参数,这个参数可以是属性的类型,指针,或者该类型的属性。例如,QWidget::enabled 有WRITE 函数QWidget::setEnabled()。 只读属性不需要WRITE 函数,如,QWidget::focus 就没有WRITE函数。
n RESET 函数是可选的。它用来将属性设置回它的上下文指定的默认值。如,QWidget::cursor 拥有典型的READ和WRITE 函数:QWidget::cursor() 和 QWidget::setCursor()。同时,还拥有RESET函数QWidget::unsetCursor(),如果没有调用QWidget::setCursor(),该函数的作用就是重置为上下文指定的光标。 RESET函数必须返回void,并且不带参数。
n NOTIFY 信号是可选的。 如果定义了,它应该指定一个在那个类中存在的信号,这个信号在属性值改变时被发射。
n DESIGNABLE 指明了该属性是否对用户界面设计工具(如,Qt Designer)的属性编辑器可见。大多数属性是DESIGNABLE的(默认为true)。 除了true或false,你还可以指定一个boolean成员函数。
n SCRIPTABLE 指明了该属性是否能被脚本引擎访问(默认为true)。除了true或false,你还可以指定一个boolean成员函数。
n STORED 指明了该属性是否单独存在或者依赖于其他值。 它同时也指明了当存储对象的状态时,属性是否被保存。 大多数的属性都是STORED的(默认值为true),但是,QWidget::minimumWidth()的STORED值为false,因为它的值仅是取自宽度组件属性QWidget::minimumSize(),它的值是一个QSize。
n USER 指明了类中该属性是否被指定为面向用户的或者用户可编辑的。通常情况下,每个类中仅有一个USER属性(其默认值为false),如QAbstractButton::checked即为(可复选)按钮的用户可编辑属性。注意,QItemDelegate能获取和设置组件的 USER属性。
n CONSTANT的存在,表明该属性是常量。 对于给定的对象实例,常量属性的READ方法每次调用的时候必须返回相同的值。 该常量的值,对于不同的对象实例可能是不同的。 常量属性不能有WRITE方法或者NOTIFY信号。
n FINAL的存在,表明该属性不能被子类覆盖。 这可以用于某些类中的性能优化,但是,并不是由moc强制执行的。 一定要注意,绝对不要覆盖FINAL属性。
READ, WRITE, 和RESET函数可以被继承。 它们也可以是virtual的。 当它们以多继承的方式被继承时,它们必须是来自第一个被继承的类。
属性的类型可以是任何被QVariant支持的类型,或者是用户自定义的类型。 下面的例子中,QDate类被视为用户自定义类型。
Q_PROPERTY(QDate date READ getDate WRITE setDate)
由于QDate是用户自定义的,所以,你在属性声明时必须包含<QDate>头文件。
对于QMap, QList,和QValueList属性,属性值是QVariant类型的,它的值是整个list或者map。注意Q_PROPERTY中不包含逗号,因为逗号会分隔宏参数。因此,你一定要使用QMap作为属性类型,而不是QMap<QString,QVariant>。 为了保持一致性,同样应该使用QList和 QValueList,而不是QList<QVariant> 和 QValueList<QVariant>。
第三章 使用元对象系统读写属性
属性可以通过通用函数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。 属性名称取自metadata,并且传给QObject::property(),用于获取在当前object中属性的value。
第四章 一个简单例子
假设我们有一个MyClass类,它继承自QObject,并且在private域中使用了Q_OBJECT宏。我们要在MyClass中声明一个属性用于保存优先级(priority value)。 属性的名称将是priority,它的类型是MyClass中定义的一个枚举类型Priority。
我们在类的private域中用Q_PROPERTY()宏声明属性。 所需的 READ 函数被命名为这里,这里包括一个名为 setPriority 的WRITE函数。 枚举类型必须与使用 Q_ENUMS()注册到元对象系统。 注册一个枚举类型使枚举名在调用QObject::setProperty()时使用。我们还必须提供自己的读取和写入函数的声明。MyClass的声明如下所示:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority)
Q_ENUMS(Priority)
public:
MyClass(QObject *parent = 0);
~MyClass();
enum Priority { High, Low, VeryHigh, VeryLow };
void setPriority(Priority priority);
Priority priority() const;
};
READ函数是const函数,并返回属性类型。 WRITE函数返回 void,并且以属性类型作为参数。 元对象编译器强制执行这些规定。
我们有两种方法设置其priority属性,一个指向MyClass实例的MyClass *或者一个指向MyClass实例的QObject *:
MyClass *myinstance = new MyClass;
QObject *object = myinstance;
myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh");
在这个例子中,属性类型的枚举在MyClass类中声明,并使用 Q_ENUMS() 宏注册到元对象系统。这使得调用setProperty()时,枚举值可作为字符串形式传入。在另一个类中定义的枚举类型,需要完整的名称,如:(i.e., OtherClass::Priority), 且该类也必须继承QObject 并使用 Q_ENUMS()宏注册该枚举类型。
Q_FLAGS()宏也可以提供类似的功能。 和Q_ENUMS()类似,在注册一个枚举类型的同时,会将该类型标注为一组标记。例如,可以使用或运算合并各个标记。一个I/O类可能有Read和Write枚举值,并且QObject::setProperty()可以接受Read |Write这样的‘或’方式,此时应该使用Q_FLAGS()注册这个枚举类型。
第五章 动态的属性(Properties)
QObject::setProperty() 还可以在运行时将新属性添加到对象中。调用QObject::setProperty() 时传入属性名和属性值,如果该属性存在于QObject中,并且属性值与属性类型相匹配的话,属性值将被存储到属性中,setProperty返回 true。如果属性类型与属性值不匹配,属性将不会改变,setProperty返回false。但是,如果QObject中没有给定名称的属性(即,如 果这个属性没用Q_PROPERTY()声明,则新属性和属性值自动添加到该的 QObject,但setProperty仍返回 false。这意味着不能从setProperty返回fasle来判断属性是否被正确设置,除非你预先知道QObject中已经有该属性。
请注意动态属性的添加基于每个实例,它们会添加到 Qobject中,而不是 QMetaObject。调用QObject::setProperty()时传递一个属性名和一个无效的QVariant,可以从实例中删除该属性。
QVariant 的默认构造函数构造一个无效的 QVariant。
如同使用Q_PROPERTY().在编译时声明属性,动态属性可以用QObject::property()查询。
第六章 属性(Properties)和自定义类型
使用属性的自定义类型需要使用 Q_DECLARE_METATYPE() 宏注册,以便可以在 QVariant 对象中存储它们的值。这使他们既适合使用 Q_PROPERTY() 宏在类定义中声明静态属性,又适合在运行时动态创建属性。
第七章 给类添加附属信息
Q_CLASSINFO()宏可以连接到属性系统,为类的meta-object添加一对name-value,例如:
Q_CLASSINFO("Version", "3.0.0")
与其他原数据(meta-data)一样,类信息可以在运行时通过meta-object访问,请参阅 QMetaObject::classInfo()。
另请参阅元对象系统(Meta-Object System)、 信号和槽(Signals and Slots)、Q_DECLARE_METATYPE()、QMetaType和QVariant。