0.前言

像某些编译器提供的平台特有的属性系统(Property System)一样,Qt也提供了一个复杂的属性系统。当然,作为一个跨平台框架,Qt没有依赖那些非标准的编译器特性,比如:__property或者[property]。Qt的解决方案适用于Qt支持平台下的任何标准C++编译器。它基于元对象系统(Meta Object Sytstem),该系统还提供了信号槽用于对象间通信。(本文主要翻译自文档:https://doc.qt.io/qt-5/properties.html

属性系统在QtWidgets中用的没那么多,可能在做属性动画或者自定义控件时会用到。但在Qt Quick框架中,我们从C++注册QML的类型,自定义属性是必不可少的。

1.声明属性的要求

要声明属性,需要继承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])

(MEMBER是Qt5新增的,Qt4文档中没有,参照:https://doc.qt.io/archives/qt-4.8/properties.html) 

如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>。

2.使用元对象系统读写属性

一个属性可以使用常规函数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()来获取当前对象的属性值。

3.一个简单的例子

假设我们有一个类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_OBJECTQ_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()来注册此枚举类型。

4.动态属性

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

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

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

5.属性和自定义类型

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

6.为类添加附加信息

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

Q_CLASSINFO("Version", "3.0.0")

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

7.自己写的示例

#include <QObject>//展示自定义class/struct作为属性
class MyData
{
public:QString _str;//需要定义!=bool operator !=(const MyData&other){return _str!=other._str;}
};
Q_DECLARE_METATYPE(MyData)//要声明属性,需要继承QObject并使用Q_PROPERTY()宏
class MyClass : public QObject
{Q_OBJECT//最常见的声明方式Q_PROPERTY(int number READ getNumber WRITE setNumber NOTIFY numberChanged)//或者Qt5这样写,直接绑定一个成员,声明下notify信号Q_PROPERTY(int age MEMBER _age NOTIFY ageChanged)Q_PROPERTY(MyData data MEMBER _data NOTIFY dataChanged)
public:explicit MyClass(QObject *parent = nullptr):QObject(parent){}//可以在set get函数中进行数据过滤int getNumber() const {return _number;}void setNumber(int number){if(number!=_number){_number=number;emit numberChanged();}}signals:void numberChanged();void ageChanged();void dataChanged();private:int _number=0;int _age=0;MyData _data{"gongjianbo1992"};
};
#include <QDebug>
#include "MyClass.h"void test_property()
{MyClass mc;mc.setProperty("number",123);mc.setProperty("age",18);qDebug()<<mc.property("number").toInt()<<mc.property("age").toInt();mc.setProperty("data",QVariant::fromValue(MyData{"new data"}));qDebug()<<mc.property("data").value<MyData>()._str;//动态属性mc.setProperty("newproperty",1992);qDebug()<<mc.property("newproperty").toInt();
}
//输出结果:
//123 18
//"new data"
//1992

8.参考

官方文档:https://doc.qt.io/qt-5/properties.html

参考博客(翻译的文档):https://blog.csdn.net/liang19890820/article/details/52022714

Qt属性系统及Q_PROPERTY宏的使用相关推荐

  1. 【Qt】Q_PROPERTY():属性系统

    一.简介 Qt属性系统基于元对象Meta-Object系统,因此在使用时,需要继承QObject类并添加宏Q_OBJECT,属性除了具有类成员的功能外,还可以通过元对象系统访问,比如可以使用信号和槽机 ...

  2. QT之Qt之Q_PROPERTY宏理解

    在初学Qt的过程中,时不时地要通过F2快捷键来查看QT类的定义,发现类定义中有许多Q_PROPERTY的东西,比如最常用的QWidget的类定义: Qt中的Q_PROPERTY宏在Qt中是很常用的,那 ...

  3. Qt入门教程【QObject篇】属性系统

    属性系统 Qt提供了一个复杂的属性系统,类似于一些编译器供应商提供的属性系统.但是,作为独立于编译器和平台的库,Qt 不依赖于非标准编译器功能,如 或 .Qt解决方案适用于Qt支持的每个平台上的任何标 ...

  4. Qt学习(6)——Q_PROPERTY宏解析

    class IconEditor : public QWidget{Q_OBJECT private:QColor curColor;public:QColor penColor() const{ r ...

  5. Qt属性动画仿真QPropertyAnimation的使用

    一.QPropertyAnimation的基本使用 1.常用接口函数 setTargetObject:设置仿真对象 setPropertyName:设置仿真属性的名称, setDuration:设置仿 ...

  6. Qt pro文件下跨平台宏的使用(windows/linux 以及x86 和 arm的区分)

    #Qt pro文件下跨平台宏的使用(windows/linux 以及x86 和 arm的区分) 在pro文件中添加: #仅在linux 系统下, 硬件平台无关的内容 unix{HEADERS += \ ...

  7. [Qt教程] 第47篇 进阶(七) 定制Qt帮助系统

    [Qt教程] 第47篇 进阶(七) 定制Qt帮助系统 楼主  发表于 2013-10-7 09:32:39 | 查看: 93| 回复: 0 定制Qt帮助系统 版权声明 该文章原创于Qter开源社区(w ...

  8. 虚幻4属性系统(反射)翻译

    http://www.cnblogs.com/ghl_carmack/p/5698438.html 2016-07-23 12:50 by 风恋残雪, 457 阅读, 3 评论, 收藏, 编辑 反射是 ...

  9. 深度探索QT窗口系统——几何篇

    深度探索QT窗口系统--几何篇 窗口作为界面编程中最重要的部分,没有窗口就没有界面,是窗口让我们摆脱了DOS时代,按钮是一个窗口,文本框是一个窗口,标签页是一个窗口.一个窗口可以由多个窗口组成,每天我 ...

最新文章

  1. CSS中连接属性的排序
  2. matlab 矩阵序列R6(n),MATLAB___09年试题加答案
  3. Layui中Jquery动态设置的select标签加载时而正常时而失效问题排查和解决
  4. 【深度学习】PyTorch深度学习技术生态
  5. 微信打开页面底部显示“原页面已由QQ浏览器云转码”解决办法
  6. (80)Verilog HDL测试激励:保存波形文件
  7. 自实现进程管理器linux,【Linux工具篇】supervisor进程管理器
  8. 近一半的智能手机受高通 Snapdragon 漏洞影响
  9. 单模光纤和多模光纤的区别_一分钟了解光纤、单模光纤、多模光纤
  10. linux卸载文件挂载,磁盘文件挂载与卸载
  11. OpenSSL 1.0.0生成p12、jks、crt等格式证书的命令个过程(转)
  12. android开发 问卷调查案例_android 实现调查问卷-单选-多选
  13. 构造体中变量后面的冒号_flow中问号在参数后面和在冒号有什么区别?declare type的作用是?看英文文档有点一知半解...
  14. der解码规则_JAVA解析各种编码密钥对(DER、PEM、openssh公钥) | 学步园
  15. 【网站介绍】有意思的网站(备忘)
  16. 莫那什大学柯秋红老师招收计算机视觉/机器学习方向博士研究生
  17. 在线编码工具_每个新编码员都需要25种工具
  18. Java 8 Stream 总结
  19. win7开启uasp协议_移植win8通用USB驱动到win7上并开启UASP功能!
  20. Linux驱动开发8 platform驱动分隔、分离与分层

热门文章

  1. 驼峰命名法和下划线命名法互转
  2. python中GIL和线程与进程
  3. Word2010怎么给文档设置权限用密码进行加密防止他人查看
  4. Linux 监控工具之Cacti使用详解(一)
  5. html高度塌陷问题
  6. Win10系统电脑鼠标左键不灵敏、有延迟怎么办
  7. CSS和CSS3选择器
  8. VS2005的R6034错误 (转载)
  9. STM32 GPIOx_CRL/GPIOx_CRH 寄存器的设置的简化描述
  10. 让你能进“大厂”的数据分析项目是长怎样?全套路线(建议收藏)