【Qt开发流程】之对象模型2:属性系统

描述

Qt提供了一个复杂的属性系统,类似于一些编译器供应商提供的属性系统。然而,作为一个独立于编译器和平台的库,Qt不依赖于非标准的编译器特性,如__property[property]
Qt解决方案适用于Qt支持的所有平台上的任何标准c++编译器。它基于元对象系统,该系统还通过信号和槽提供对象间通信。

声明属性要求

要声明属性,请在继承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函数。该类型可以是QVariant支持的任何类型,也可以是用户定义的类型。其他项是可选的,但WRITE函数是常见的。除USER外,其他属性默认为false。

例如:

Q_PROPERTY(QString title READ title WRITE setTitle USER true)

下面是取自类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访问器函数(但不能同时使用)。
  • RESET函数是可选的。它用于将属性设置回上下文特定的默认值。例如,QWidget::cursor具有典型的READ和WRITE函数(QWidget::cursor()和QWidget::setCursor()),它还有一个RESET函数QWidget::unsetCursor(),因为对QWidget::setCursor()的调用没有意义。RESET函数必须返回void并且不接受参数。
  • NOTIFY信号是可选的。如果定义了,它应指定该类中的一个现有信号,该信号在属性值更改时发出。MEMBER变量的NOTIFY信号必须接受零个或一个参数,该参数的类型必须与属性的类型相同。参数将采用属性的新值。只有在属性实际上发生更改时,才应该发出NOTIFY信号,以避免在QML中不必要地重新评估绑定,例如。当需要为未具有显式设置器的MEMBER属性自动发出该信号时,Qt会自动发出该信号。
  • REVISION编号是可选的。如果包含,则定义了属性及其通知器信号在API的特定修订版中使用(通常用于暴露给QML)。如果不包含,则默认为0。
  • DESIGNABLE属性指示属性是否应在GUI设计工具(例如Qt Designer)的属性编辑器中可见。大多数属性都是DESIGNABLE的(默认为true)。可以指定一个布尔成员函数代替true或false。
  • SCRIPTABLE属性指示该属性是否应由脚本引擎访问(默认为true)。您可以指定一个布尔成员函数代替true或false。
  • STORED属性指示该属性是否应被视为存在于自己或依赖于其他值上。它还指示在存储对象状态时是否必须保存属性值。大多数属性都是STORED的(默认为true),但是例如,QWidget::minimumWidth()的STORED为false,因为它的值只是从属性QWidget::minimumSize()的宽度组件中获取的,后者是一个QSize。
  • USER属性指示该属性是否指定为类的用户界面或可编辑属性。通常,每个类只有一个USER属性(默认为false)。例如,QAbstractButton::checked是(可检查)按钮的可编辑属性。请注意,QItemDelegate会获取和设置部件的USER属性。
  • CONSTANT属性的存在表示属性值是常量的。对于给定的对象实例,常量属性的READ方法在每次调用时必须返回相同的值。该常量值可以在对象的不同实例间有所不同。常量属性不能具有WRITE方法或NOTIFY信号。
  • FINAL属性的存在表示该属性不会被派生类覆盖。在某些情况下,这可以用于性能优化,但是不受moc的强制执行。永远不要覆盖FINAL属性。

可以继承READ、WRITE和RESET功能。它们也可以是虚拟的。当它们在使用多重继承的类中被继承时,它们必须来自第一个继承的类。

属性类型可以是QVariant支持的任何类型,也可以是用户定义的类型。在本例中,类QDate被认为是用户定义的类型。


Q_PROPERTY(QDate date READ getDate WRITE setDate)

因为QDate是用户定义的,所以必须包含 带有属性声明的头文件。

对于QMap、QList和QValueList属性,属性值是一个QVariant,其值是整个列表或映射。注意,Q_PROPERTY字符串不能包含逗号,因为逗号分隔了宏参数。因此,必须使用QMap作为属性类型,而不是QMap<QString,QVariant>。
为了保持一致性,也使用QList和QValueList而不是 QList和QValueList。

使用元对象系统对属性进行读写操作

可以使用泛型函数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,其类型将是一个名为priority的枚举类型,该类型在MyClass中定义。

我们在类的私有部分使用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实例的指针或一个指向MyClass实例的QObject指针,我们有两种方法来设置其优先级属性:

MyClass *myinstance = new MyClass;
QObject *object = myinstance;myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh");
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << myinstance->property("priority");

在这里插入图片描述
当把

object->setProperty("priority", "VeryHigh");

改为

object->setProperty("priority", "VeryHi");

再次输出,会输出为空,会自行检查,是安全的。
在这个例子中,作为属性类型的枚举类型在MyClass中声明,并使用Q_ENUM()宏在元对象系统中注册。这使得枚举值可以作为字符串使用,就像在调用setProperty()时一样。如果枚举类型在另一个类中声明,则需要它的完全限定名(即OtherClass::Priority),并且其他类也必须继承QObject并使用Q_ENUM()宏在那里注册枚举类型。

还有一个类似的宏Q_FLAG()。像Q_ENUM()一样,它注册了一个枚举类型,但它将该类型标记为一组标志,即可以将值OR在一起。一个I/O类可能有枚举值Read和Write,然后QObject::setProperty()可以接受Read | Write。Q_FLAG()应该用来注册这个枚举类型。

动态属性

QObject::setProperty()也可用于在运行时为类的实例添加新属性。当使用名称和值调用它时,如果QObject中存在具有给定名称的属性,并且给定的值与属性的类型兼容,则该值将存储在属性中,并返回true。如果该值与属性的类型不兼容,则不会更改属性,并返回false。但是,如果具有给定名称的属性在QObject中不存在(即,如果它没有使用Q_PROPERTY()声明),则一个具有给定名称和值的新属性将自动添加到QObject中,但仍然返回false。这意味着不能使用返回false来确定是否实际设置了特定属性,除非您事先知道该属性已经存在于QObject中。

注意,动态属性是在每个实例的基础上添加的,也就是说,它们被添加到QObject,而不是QMetaObject。通过将属性名称和无效的QVariant值传递给QObject::setProperty(),可以从实例中删除属性。QVariant的默认构造函数构造了一个无效的QVariant。

动态属性可以用QObject::property()查询,就像在编译时用Q_PROPERTY()声明的属性一样。

属性和自定义类型

属性使用的自定义类型需要使用Q_DECLARE_METATYPE()宏注册,以便它们的值可以存储在QVariant对象中。这使得它们既适合与类定义中使用Q_PROPERTY()宏声明的静态属性一起使用,也适合与运行时创建的动态属性一起使用。

向类添加附加信息

连接到属性系统的是一个额外的宏Q_CLASSINFO(),它可以用来将额外的名值对附加到类的元对象上,例如:

Q_CLASSINFO("Version", "3.0.0")

与其他元数据一样,类信息可以在运行时通过元对象访问;详情可以参考QMetaObject::classInfo()

使用场景

  1. 自定义控件:通过属性系统,可以方便地添加自定义控件所需要的属性,并在运行时动态修改其属性值,以实现控件的个性化定制。

  2. 翻译:在多语言支持的应用程序中,可以将所有需要翻译的字符串作为属性添加到组件中,这样可以方便地将这些字符串全部翻译成不同的语言。

  3. 皮肤定制:通过属性系统,可以在运行时动态地修改控件的样式属性,以实现界面的不同皮肤定制。
    比如改变样式:

{// 创建QPushButton控件QPushButton *button = new QPushButton("Click me", this);// 添加样式属性button->setProperty("buttonColor", QColor(220, 220, 220));button->setProperty("buttonTextColor", QColor(50, 50, 50));// 设置初始样式setButtonStyle(button);// 在运行时修改样式属性button->setProperty("buttonColor", QColor(0, 255, 0));button->setProperty("buttonTextColor", QColor(255, 0, 0));setButtonStyle(button); // 重新设置样式
}// 定义函数来根据样式属性设置样式表
void setButtonStyle(QPushButton* button) {QColor buttonColor = button->property("buttonColor").value<QColor>();QColor buttonTextColor = button->property("buttonTextColor").value<QColor>();button->setStyleSheet(QString("QPushButton { background-color: rgb(%1, %2, %3); color: rgb(%4, %5, %6); }").arg(buttonColor.red()).arg(buttonColor.green()).arg(buttonColor.blue()).arg(buttonTextColor.red()).arg(buttonTextColor.green()).arg(buttonTextColor.blue()));
}

在这里插入图片描述

属性系统的优势

  1. 灵活性:通过属性系统,可以轻松地在运行时动态修改对象的属性值,从而实现动态控制组件的行为。

  2. 扩展性:开发人员可以轻松地为自定义的控件添加属性,并通过属性系统来进行控制,从而扩展控件的属性和功能。

  3. 易用性:Qt属性系统提供了简单易用的API,开发人员可以方便地使用属性系统来管理和操作对象的属性。

结论

年轻人嘛,现在没钱算什么,以后没钱的日子还多着呢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/199785.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣第374场周赛题解

这一场周赛的题目是比较难的一次&#xff0c;写了1个多小时就写了两个题目。 首先第一题&#xff1a; 纯水题&#xff0c;遍历然后进行一下判断就可以解决了。这边就不放代码了。 第二题&#xff1a; 这个题目&#xff0c;我觉得难度非常大&#xff0c;其实代码量也不大都是很…

IOday3作业

1> 使用文件IO完成对图像的读写操作 #include<myhead.h>int main(int argc, const char *argv[]) {//只读打开图片int fd-1;if((fd open("./R-C.bmp",O_RDWR))-1){perror("open");return -1;}//向后便宜两个字节找到大小的起始地址lseek(fd,2,S…

【数据结构】二叉树的实现

目录 1. 前言2. 二叉树的实现2.1 创建一棵树2.2 前序遍历2.2.1 分析2.2.2 代码实现2.2.3 递归展开图 2.3 中序遍历2.3.1 分析2.3.2 代码实现2.3.3 递归展开图 2.4 后序遍历2.4.1 分析2.4.2 代码实现2.4.3 递归展开图 2.5 求节点个数2.5.1 分析2.5.2 代码实现 2.6 求叶子节点个数…

Linux 调试器 --- g d b 使用

目录 一&#xff1a;gdb简介 二&#xff1a;示例代码 三&#xff1a;使用 1.启动gdb 2.各种指令 <1>: 查看源代码 <2>:设置断点 <3>:查看断点信息 <4>:删除断点 <5>: run <6>:逐过程调试 <7>:逐语句调试 <8>:查…

TrustZone​之在安全状态之间切换

如果处理器处于NS.EL1,而软件想要转移到S.EL1,应该如何实现呢? 要改变安全状态,无论是向上还是向下,执行都必须经过EL3,如下图所示: 前面的图表显示了在不同安全状态之间移动涉及的步骤的示例序列。逐步进行解释: 进入较高的异常级别需要一个异常。通常,此异常…

相关基础知识

本文引注&#xff1a; https://zhuanlan.zhihu.com/p/447221519 1.方差 2.自协方差矩阵 3.自相关矩阵 4.互协方差矩阵 5.互相关矩阵 6.相关系数 7.自相关函数、自协方差函数与功率谱密度 8.互相关函数、互协方差函数与互功率谱密度

时间选择器

<el-form-item label"时间范围"><!-- <el-date-picker size"small"v-model"createTime"type"daterange"range-separator"至"start-placeholder"请输入起始创建时间"end-placeholder"请输入终止创…

无线网优AP、SW发现控制器

目录 无线网优解决的问题 1、信号覆盖不足的原因 2、信道繁忙 3、非802.11干扰 4、协商速率低 5、漫游效果差 6、有线带宽阻塞 无线网优方法 交换机发现与激活 一&#xff0c;交换机发现控制器方式 1、二层广播 2、DHCP option43方式 3、DNS域名解析方式 4、trou…

基于springboot + vue大学生竞赛管理系统

qq&#xff08;2829419543&#xff09;获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;springboot 前端&#xff1a;采用vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xf…

【AIGC】AI作图最全提示词prompt集合(收藏级)

目录 一、正向和负向提示词 二、作图参数 你好&#xff0c;我是giszz. AI做图真是太爽了&#xff0c;解放生产力&#xff0c;发展生产力。 但是&#xff0c;你是不是也总疑惑&#xff0c;为什么别人的图&#xff0c;表现力那么丰富呢&#xff0c;而且指哪打哪&#xff0c;要…

DFT(离散傅里叶变换)的通俗理解

本文包含了博主对离散傅里叶变换&#xff0c;负频率&#xff0c;实信号与复信号频谱的理解&#xff0c;如有不妥&#xff0c;欢迎各位批评指正与讨论。 文章目录 DFT的理解信号的频谱实信号的频谱复信号的频谱 DFT的理解 傅里叶变换是一种将信号从时域转换到频域的数学工具。…

通信标准化协会,信通院及量子信息网络产业联盟调研玻色量子,共绘实用化量子未来!

8月14日&#xff0c;中国通信标准化协会&#xff0c;信通院标准所及量子信息网络产业联盟等单位领导走访调研北京玻色量子科技有限公司&#xff08;以下简称“玻色量子”&#xff09;&#xff0c;参观了玻色量子公司及自建的十万颗粒洁净度的光量子信息技术实验室&#x1f517;…

STM32下载程序的五种方法

刚开始学习 STM32 的时候&#xff0c;很多小伙伴满怀热情买好了各种设备&#xff0c;但很快就遇到了第一个拦路虎——如何将写好的代码烧进去这个黑乎乎的芯片&#xff5e; STM32 的烧录方式多样且灵活&#xff0c;可以根据实际需求选择适合的方式来将程序烧录到芯片中。本文将…

10年前,我就用 SQL注入方式发现了学校网站的漏洞

大家好&#xff0c;我是风筝。 事情是这样子的&#xff0c;在10年以前&#xff0c;某个月黑风高夜的夜里&#xff0c;虽然这么说有点暴露年龄了&#xff0c;但无所谓&#xff0c;毕竟我也才18而已。我打开电脑&#xff0c;在浏览器中输入我们高中学校的网址&#xff0c;页面很…

TCP首部格式_基本知识

TCP首部格式 表格索引: 源端口目的端口 序号 确认号 数据偏移保留 ACK等 窗口检验和紧急指针 TCP报文段首部格式图 源端口与目的端口: 各占16位 序号:占32比特&#xff0c;取值范围0~232-1。当序号增加到最后一个时&#xff0c;下一个序号又回到0。用来指出本TCP报文段数据载…

ctfhub技能树_web_信息泄露

目录 二、信息泄露 2.1、目录遍历 2.2、Phpinfo 2.3、备份文件下载 2.3.1、网站源码 2.3.2、bak文件 2.3.3、vim缓存 2.3.4、.DS_Store 2.4、Git泄露 2.4.1、log 2.4.2、stash 2.4.3、index 2.5、SVN泄露 2.6、HG泄露 二、信息泄露 2.1、目录遍历 注&#xff1…

【ArcGIS Pro微课1000例】0050:如何清除坐标系信息

文章目录 一、目的二、方法1. 使用【定义投影】工具2. 清除数据的投影信息3. 删除坐标文件 一、目的 地理信息数据的坐标系是将地理信息数据进行融合、叠加、分析的重要数学框架&#xff0c;而其描述信息是非常重要的元数据&#xff0c;涉及整个国家的测绘坐标系统&#xff0c…

DSSS技术和OFDM技术

本内容为学习笔记&#xff0c;内容不一定正确&#xff0c;请多处参考进行理解 https://zhuanlan.zhihu.com/p/636853588 https://baike.baidu.com/item/OFDM/5790826?frge_ala https://zhuanlan.zhihu.com/p/515701960?utm_id0 一、 DSSS技术 信号替代&#xff1a;DSSS技术为…

C++ 操作MinIO做文件数据的上传和下载(踩坑与经验)包含编译包

前言 最近在做项目流程优化&#xff0c;准备将之前的java对文件的操作转换到c端&#xff0c;因此做了基于c的minio操作的测试demo。期间的各种踩坑与问题&#xff0c;花了一天时间总算是成功了&#xff0c;当然还有一些小问题&#xff0c;等待后续其他大拿解决。 项目环境 v…

Jmeter调用本地Exe程序

背景&#xff1a; 候使用C#编译的小工具制作压测的请求的入参&#xff0c;因Jmeter无法调用C#的方法&#xff0c;可以把C#编译个exe程序&#xff0c;在启动压测前&#xff0c;让JMeter调用本地的exe批量生成压测数据。 使用步骤&#xff1a; 打开Jmeter&#xff0c;右击选择…