简述

Qt提供一个类似于其它编译器供应商提供的复杂属性系统(Property System)。然而,作为一个编译器和平台无关的库,Qt不能够依赖于那些非标准的编译器特性,比如:__property或者[property]。Qt的解决方案适用于Qt支持平台下的任何标准C++编译器。它依赖于元对象系统(Meta Object Sytstem) - 通过信号和槽提供对象间通讯机制。

声明属性的要求

要声明一个属性,在继承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;

一个属性的行为就像一个类的数据成员,但它有通过元对象系统访问的附加功能。

  • 如果MEMBER关键字没有被指定,则一个READ访问函数是必须的。它被用来读取属性值。理想的情况下,一个const函数用于此目的,并且它必须返回的是属性类型或const引用。比如:QWidget::focus是一个只读属性,通过READ函数QWidget::hasFocus()访问。

  • 一个WRITE访问函数是可选的,用于设置属性的值。它必须返回void并且只能接受一个参数,属性的类型是类型指针或引用,例如:QWidget::enabled具有WRITE函数QWidget::setEnabled()。只读属性不需要WRITE函数,例如:QWidget::focus没有WRITE函数。

  • 如果READ访问函数没有被指定,则MEMBER变量关联是必须的。这使得给定的成员变量可读和可写,而不需要创建READ和WRITE访问函数。如果需要控制变量访问,仍然可以使用READ和WRITE函数而不仅仅是MEMBER(但别同时使用)。

  • 一个RESET函数是可选的,用于将属性设置为上下文指定的默认值。例如:QWidget::cursor有READ和WRITE函数QWidget::cursor()和QWidget::setCursor(),同时也有一个RESET函数QWidget::unsetCursor(),因为没有可用的QWidget::setCursor()调用可以确定的将cursor属性重置为上下文默认的值。RESET函数必须返回void类型,并且不带任何参数。

  • 一个NOTIFY信号是可选的。如果定义了NOTIFY,则需要在类中指定一个已存在的信号,该信号在属性值发生改变时发射。与MEMBER变量相关的NOTIFY信号必须有零个或一个参数,而且必须与属性的类型相同。参数保存的是属性的新值。NOTIFY信号应该仅当属性值真正的发生变化时发射,以避免被QML重新评估。例如:当需要一个没有显式setter的MEMBER属性时,Qt会自动发射信号。

  • 一个REVISION数字是可选的。如果包含了该关键字,它定义了属性并且通知信号被特定版本的API使用(通常是QML);如果没有包含,它默认为0。

  • DESIGNABLE属性指定了该属性在GUI设计器(例如:Qt Designer)里的编辑器中是否可见。大多数的属性是DESIGNABLE (默认为true)。除了true或false,你还可以指定boolean成员函数。

  • SCRIPTABLE属性表明这个属性是否可以被一个脚本引擎操作(默认是true)。除了true或false,你还可以指定boolean成员函数。

  • STORED属性表明了该属性是否是独立存在的还是依赖于其它属性。它也表明在保存对象状态时,是否必须保存此属性的值。大多数属性是STORED(默认为true)。但是例如:QWidget::minmunWidth()的STROED为false,因为它的值从QWidget::minimumSize()(类型为QSize)中的width部分取得。

  • USER属性指定了属性是否被设计为用户可见和可编辑的。通常情况下,每一个类只有一个USER属性(默认为false)。例如: QAbstractButton::checked是(checkable)buttons的用户可修改属性。注意:QItemDelegate获取和设置widget的USER属性。

  • CONSTANT属性的出现表明属性是一个常量值。对于给定的object实例,常量属性的READ函数在每次被调用时必须返回相同的值。对于不同的object实例该常量值可能会不同。一个常量属性不能具有WRITE函数或NOYIFY信号。

  • FINAL属性的出现表明属性不能被派生类所重写。有些情况下,这可以用于效率优化,但不能被moc强制执行。必须注意不能覆盖一个FINAL属性。

属性类型可以是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()来获取当前对象的属性值。

一个简单的示例

假设我们有一个类MyClass,它从QObject派生并且在其private区域使用了Q_OBJECT宏。我们想在MyClass类中声明一个属性来追踪一个priority值。属性的名称是priority,它的类型是定义在MyClass中的Priority枚举。

我们在类的private区域使用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 = 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(MyClass实例)的指针时,我们有两种方法来设置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_FLAGS()也是可用的,就像Q_ENUMS(),它注册一个枚举类型,但是它把枚举类型作为一个flag集合,也就是,值可以用OR操作来合并。一个I/O类可能具有枚举值Read和Write并且QObject::setProperty()可以接受Read | Write。应使用Q_FLAGS()来注册此枚举类型。

动态属性

QObject::setProperty()也可以用来在运行时期向一个类的实例添加新的属性。当使用一个名字和值调用它时,如果QObject中一个指定名称的属性已经存在,并且如果给定的值与属性的类型兼容,那么,值就被存储到属性中,然后返回true。如果值与属性类型不兼容,属性的值就不会发生改变,会返回false。但是如果QObject中一个指定名称的属性不存在(例如:未用Q_PROPERTY()声明),一个带有指定名称和值的新属性就被自动添加到QObject中,但是依然会返回false。这意味着返回值不能用于确定一个属性是否被设置值,除非事先知道这个属性已经存在于QObject中。

注意:态属性被添加到每一个实例中,即:它们被添加到QObject中,而不是QMetaObject。一个属性可以从一个实例中删除,通过传入属性名和非法的QVariant值给QObject::setProperty()。默认的QVariant构造器会构造一个非法的QVariant。

动态属性可用QObject::property()来查询,就像使用Q_PROPERTY()声明的属性一样。

属性和自定义类型

被属性使用的自定义类型需要使用Q_DECLARE_METATYPE()宏注册,以便它们的值能被保存在QVariant对象中。这使得它们适用于在类定义时使用Q_PROPERTY()宏声明的静态属性,以及运行时创建的动态属性。

为类添加附加信息

与属性系统相对应的是一个附加宏 - Q_CLASSINFO()。用于添加name-value对到类的元对象中。例如:

Q_CLASSINFO("Version", "3.0.0")

和其它meta-data一样,类信息可以在运行时通过meta-object访问,详情见:QMetaObject::classInfo() 。

更多参考

  • The Property System - 助手

Qt之属性系统的更多相关文章

  1. 解析Qt元对象系统(四) 属性系统(确实比较方便)

    官方解释 我们在Qt源码中可以看到一个QObject的子类经常会用到一些Q_开头的宏,例如QMainWindow类开始部分代码是这样的: Q_PROPERTY(QSize iconSize READ ...

  2. Qt元对象和属性系统详解

    Qt 是一个用标准 C++ 编写的跨平台开发类库,它对标准 C++ 进行了扩展,引入了元对象系统.信号与槽.属性等特性,使应用程序的开发变得更高效. 本节将介绍 Qt 的这些核心特点,对于理解和编写高 ...

  3. Qt 中的属性系统(Property System)

    21 人赞同了该文章 本节内容主要讲解我对 Qt 属性系统的理解.官方文档参考 The Property System. 如何理解"属性系统"这个概念? 一般我们说一个类有什么属性 ...

  4. Qt属性系统

    The Property System Qt提供一个类似于其他编译器供应商提供的精致的属性系统.然而,作为一个编译器和平台独立的库,Qt并不依赖于非标准编译器特性,如__property 或 [pro ...

  5. QtCore是Qt的精髓(包括五大模块:元对象系统,属性系统,对象模型,对象树,信号槽)

    作者:小豆君的干货铺链接:https://www.zhihu.com/question/27040542/answer/218384474来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业 ...

  6. Qt属性系统(Qt Property System)

    Qt提供了巧妙的属性系统,它与某些编译器支持的属性系统相似.然而,作为平台和编译器无关的库,Qt不能够依赖于那些非标准的编译器特性,比如__property 或者 [property].Qt的解决方案 ...

  7. Qt之资源系统

    简述 Qt 的资源系统用于存储应用程序的可执行二进制文件,它采用平台无关的机制.当你的程序总需要这样的一系列文件(图标.翻译文件等)并且不想冒丢失某些文件的风险时,这就显得十分有用. 资源系统基于 q ...

  8. QT的Paint 系统

    下面对于QT的绘制系统做一个简要说明, 这个系统主要由三部分组成,  QPainter, QPaintDevice, QPaintEngine. QPainter 是一个绘制接口类,提供绘制各种面向用 ...

  9. Qt 元对象系统(Meta-Object System)(不管是否使用信号槽,都推荐使用)

    Qt 元对象系统(Meta-Object System) Qt的元对象系统基于如下三件事情: 类:QObject,为所有需要利用原对象系统的对象提供了一个基类. 宏:Q_OBJECT,通常可以声明在类 ...

随机推荐

  1. UVA 590 二十一 Always on the run

     Always on the run Time Limit:3000MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Submit ...

  2. 【mark】自己整合的vi/vim命令

    又发现一篇很好的 http://blog.chinaunix.net/uid-16759545-id-4891666.html 又发现一个很好的系列:有空闲要精读一下: http://www.cnbl ...

  3. Map Columns From Different Tables and Create Insert and Update Statements in Oracle Forms

    This is one of my most needed tool to create Insert and Update statements using select or alias from ...

  4. VEP安装指南

    #下载依赖包 sudo apt-get install -y curl rsync tar make perl perl-base tabix #设置perl环境变量 export PERL_PATH ...

  5. ABAP DESCRIBE语句

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  6. hdu 4828 Grids 卡特兰数+逆元

    Grids Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others) Problem D ...

  7. return、 return false的用法

    1. return返回null,起到中断方法执行的效果,只要不return false事件处理函数将会继续执行,表单将提交2. return false,事件处理函数会取消事件,不再继续向下执行.比如 ...

  8. JMS【三】--ActiveMQ简单的HelloWorld实例

    第一篇博文JMS[一]--JMS基本概念,我们介绍了JMS的两种消息模型:点对点和发布订阅模型,以及消息被消费的两个方式:同步和异步,JMS编程模型的对象,最后说了JMS的优点. 第二篇博文JMS[二 ...

  9. c++ vector 简单实现。

    第二次修改: 1)熟悉基本的模板编程,头文件和定义必须放到一起. 2)熟悉内存管理模板类 allocator<T>. 1.使用标准库的内存管理类 allocator<T> 代替 ...

  10. poj2451Uyuw's Concert(半平面交)

    链接 逆时针给出线段,如果模板是顺时针的修改下系数的符号进行平面交即可. #include <iostream> #include<cstdio> #include<cs ...