简述

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. jQuerySchool

    jQUery

  2. HQL语句大全(转载)

    Hibernate配备了一种非常强大的查询语言,这种语言看上去很像SQL.但是不要被语法结构 上的相似所迷惑,HQL是非常有意识的被设计为完全面向对象的查询,它可以理解如继承.多态 和关联之类的概念. ...

  3. poj 1654 Area (多边形求面积)

    链接:http://poj.org/problem?id=1654 Area Time Limit: 1000MS   Memory Limit: 10000K Total Submissions:  ...

  4. Python基础学习笔记(一)入门

    参考资料: 1. <Python基础教程> 2. http://www.runoob.com/python/python-chinese-encoding.html 3. http://w ...

  5. select into from 和 insert into select 的区别和用法及 SQL SELECT INTO 中Undeclared variable错误解决办法

    今天试了一下数据表中的数据备份到另一个空的数据表,然后使用了SQL SELECT INTO语句,然后提示Undeclared variable......错误,现在在这里做下总结并给出解决办法. 应用 ...

  6. springaop实现登陆验证

    1.首先配置好springmvc和springaop 2.先写好登陆方法,通过注解写代理方法 通过代理获得登陆方法的参数方法名,然后再aop代理方法内进行登陆验证 贴出代码 package com.h ...

  7. [转载] 淘宝内部分享:怎么跳出MySQL的10个大坑(上)

    原文: http://mp.weixin.qq.com/s?__biz=MzAxNjAzMTQyMA==&mid=209773318&idx=1&sn=e9600d3db80b ...

  8. Could not initialize class sun.awt.X11GraphicsEnvironment

    报错:

  9. 关于Socket的经验小结

    前言 IM通信在互联网发展到现在已经是码农的世界里人尽皆知的技术,特别在当下移动互联网迅猛发展的时代这种技术的开发也更加火热,其中老牌的代表作就有QQ和MSN,和最近新崛起的微信,默默,易信,来往等眼 ...

  10. web前端学习(一) html+js实现文本框背景及只读属性修改

    因为工作需求接触了html+javascript,对于从事嵌入式开发,一直接触c和汇编的我,感觉这种语言是一个的全新的领域,宽松的语法要求,等同于文本逻辑的输出,当然我并不认为它简单,错误检查的缺少让 ...