Qt 属性系统记录
- 一、概述
- 二、属性声明
- 三、通过元对象系统读写属性
- 四、简单例子
- 五、动态属性
- 六、对一个类添加额外的属性
一、概述
Qt 提供了一个复杂的属性系统,类似于一些编译器供应商提供的系统。然而,作为一个独立于编译器和平台的库,Qt并不依赖于诸如 __property 或 [property] 这样的非标准编译器特性。Qt 解决方案适用于Qt支持的所有平台上的任何标准c++编译器。属性系统基于元对象系统,元对象系统还通过信号和槽提供对象间通信。
属性在哪里是被用到的呢?属性我一般用的多是通过 qml 来与 c++ 交互的时候,数据交互的时候使用。
属性在 Qt 的帮助文档里看到是这样的,就是 Properties.
二、属性声明
声明也很简单,要声明属性,请在 继承 QObject的类 中使用Q_PROPERTY()宏,继承的类必须继承QObject 。
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])
每一个 像 Read 后面的都是跟的一个函数哈,
下面是一些取自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属性。
注意,必须指定一个通知信号以允许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:如果没有指定成员变量,则需要一个读访问器函数。它用于读取属性值。理想情况下,const函数用于此目的,它必须返回属性的类型或指向该类型的const引用。例如,QWidget::focus是一个只读属性,具有读取函数QWidget::hasFocus()。
- WRITE: 写访问器函数是可选的。它用于设置属性值。它必须返回void,而且必须只有一个参数,要么是属性的类型,要么是指向该类型的指针或引用。例如,QWidget::enabled具有写函数QWidget::setEnabled()。只读属性不需要写函数。例如,QWidget::focus没有写功能。
- MEMBER: 如果没有指定读访问器函数,则必须关联成员变量。这使得给定的成员变量可读可写,而无需创建读写访问器函数。如果你需要控制变量访问,除了关联成员变量之外,也可以使用读写访问器函数(但不能同时使用两者)。
- RESET:重置功能是可选的。它用于将属性设置回其上下文特定的默认值。例如,QWidget::cursor具有典型的读写函数,QWidget::cursor()和QWidget::setCursor(),它还有一个重置函数,QWidget::unsetCursor(),因为没有调用QWidget::setCursor()可能意味着重置上下文特定的游标。RESET函数必须返回void且不接受任何参数。
- NOTIFY: 通知信号是可选的。如果定义了,它应该指定类中存在的一个信号,每当属性的值发生变化时,该信号就会发出。成员变量的通知信号必须接受零个或一个参数,该参数的类型必须与属性相同。这个参数将接受属性的新值。NOTIFY信号只应该在属性真正发生更改时发出,以避免在QML中不必要地重新计算绑定。当需要的成员属性没有显式设置方法时,Qt会自动发出这个信号。
- REVISION: 版本号是可选的。如果包含,它将定义在特定版本的API(通常用于QML)中使用的属性及其通知器信号。如果不包含,则默认为0。
- DESIGNABLE:属性表示该属性是否应该在GUI设计工具(例如Qt Designer)的属性编辑器中可见。大多数属性都是可设计的(默认为true)。除了true或false之外,还可以指定一个布尔成员函数。
- SCRIPTABLE:属性表示脚本引擎是否可以访问该属性(默认为true)。除了true或false之外,还可以指定一个布尔成员函数。
- STORED:属性表示该属性是独立存在的,还是依赖于其他值。它还指示在存储对象的状态时是否必须保存属性值。大多数属性都是被存储的(默认值为true),但例如QWidget::minimumWidth()被存储为false,因为它的值是从属性QWidget::minimumSize()的宽度组件中获取的,这是一个QSize。
- USER:属性指示该属性是指定为类的面向用户属性还是用户可编辑属性。通常,每个类只有一个USER属性(默认为false)。例如,QAbstractButton::checked是(可检查)按钮的用户可编辑属性。注意,QItemDelegate获取和设置小部件的USER属性。
- CONSTANT:常量属性的出现表明该属性的值是常量。对于给定的对象实例,常量属性的READ方法必须每次调用都返回相同的值。这个常量的值可能因对象的不同实例而不同。常量属性不能有写方法或通知信号。
- FINAL:最终属性的存在表明该属性不会被派生类覆盖。这在某些情况下可用于性能优化,但moc并不强制执行。必须
注意,永远不要覆盖FINAL属性。
READ、WRITE和RESET函数可以继承。它们也可以是虚拟的。在使用多重继承的类中继承时,它们必须来自第一个继承的类。
属性类型可以是QVariant支持的任何类型,也可以是用户定义的类型。在这个例子中,类QDate被认为是一个用户定义的类型。
Q_PROPERTY(QDate date READ getDate WRITE setDate)
因为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() 用于获取关于某个未知类中定义的每个属性的元数据。属性名从元数据中获取,并传递给 QObject::property() 以获得当前对象中属性的值。
四、简单例子
假设我们有一个MyClass类,它派生自QObject,并在其私有部分中使用Q_OBJECT宏。我们想在MyClass中声明一个属性来跟踪优先级值。属性的名称是priority,类型是在MyClass中定义的枚举类型priority。
我们在类的private部分使用Q_PROPERTY()宏声明属性。所需的读取函数名为priority,我们还包含了一个名为setPriority的写入函数。枚举类型必须使用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实例的指针或一个指向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_FLAG()。与Q_ENUM()一样,它注册了一个枚举类型,但它将该类型标记为一组标志,即可以组合在一起的值。I/O类可能有可读和可写的枚举值,然后QObject::setProperty()可以接受读|写。应该使用Q_FLAG()来注册这种枚举类型。
五、动态属性
QObject::setProperty()也可以用于在运行时为类的实例添加新属性。当使用一个名称和一个值调用它时,如果QObject中存在一个具有给定名称的属性,并且给定的值与属性的类型兼容,则该值将存储在属性中,并返回true。如果值与属性的类型不兼容,则不会修改属性,返回false。但是,如果QObject中不存在给定名称的属性(即,如果没有使用Q_PROPERTY()声明它),则会自动将具有给定名称和值的新属性添加到QObject中,但仍然会返回false。这意味着返回false不能用来确定某个属性是否被设置,除非你事先知道该属性已经存在于QObject中。
请注意,动态属性是在每个实例的基础上添加的,即它们是添加到QObject,而不是QMetaObject。通过向QObject::setProperty()传递属性名和无效的QVariant值,可以从实例中删除属性。QVariant的默认构造函数构造了一个无效的QVariant。
可以使用QObject::property()查询动态属性,就像使用Q_PROPERTY()在编译时声明的属性一样。
六、对一个类添加额外的属性
连接到属性系统的是一个额外的宏,Q_CLASSINFO(),可用于将额外的名称—值对附加到类的元对象,这个属性的话就是自己加的哈,例如:
Q_CLASSINFO("Version", "3.0.0")
具体在类的写法:
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "Sabrina Schweinsteiger")
Q_CLASSINFO("url", "http://doc.moosesoft.co.uk/1.0/")
public:
...
};