【Qt开发流程】之对象模型1:信号和槽

Qt对象模型

标准c++对象模型为对象范型提供了非常有效的运行时支持。但是它的静态特性在某些问题领域是不灵活的。图形用户界面编程是一个既需要运行时效率又需要高度灵活性的领域。Qt通过结合c++的速度和Qt对象模型的灵活性提供了这一点。
Qt将这些特性添加到c++中:

    • 一个非常强大的无缝对象通信机制,称为信号和插槽
    • 可查询和可设计的对象属性
    • 强大的事件和事件过滤器
    • 用于国际化的上下文字符串转换
    • 复杂的间隔驱动计时器,可以在事件驱动的GUI中优雅地集成许多任务
    • 分层和可查询的对象树,以自然的方式组织对象所有权
    • 保护指针(QPointer),当引用的对象被销毁时自动设置为0,不像普通的c++指针在其对象被销毁时变成悬空指针
    • 跨库边界工作的动态强制转换。
    • 支持自定义类型创建。

Qt的许多特性都是用标准的c++技术实现的,基于对QObject的继承。其他的,如对象通信机制和动态属性系统,需要Qt自己的元对象编译器(moc)提供的元对象系统。
元对象系统是一个c++扩展,它使该语言更适合于真正的组件GUI编程。

信号和槽

信号和插槽用于对象之间的通信。信号和槽机制是Qt的核心特性,可能也是与其他框架提供的特性最不同的部分。信号和槽是由Qt的元对象系统实现的。

介绍

在GUI编程中,当我们更改一个小部件时,我们通常希望通知另一个小部件。更一般地说,我们希望任何类型的对象都能够相互通信。例如,如果用户单击Close按钮,我们可能希望调用窗口的Close()函数。
其他工具包使用回调实现这种通信。回调是一个指向函数的指针,所以如果你想让一个处理函数通知你一些事件,你可以把一个指向另一个函数(回调)的指针传递给处理函数。然后,处理函数在适当的时候调用回调。虽然确实存在使用此方法的成功框架,但回调可能不直观,并且可能在确保回调参数的类型正确性方面存在问题。

信号与槽

在Qt中,我们有一个回调技术的替代方案:我们使用信号和槽。当一个特定的事件发生时,就会发出一个信号。Qt的小部件有许多预定义的信号,但是我们总是可以子类化小部件,向它们添加我们自己的信号。插槽是响应特定信号而调用的函数。Qt的小部件有许多预定义的插槽,但是通常的做法是子类化小部件并添加您自己的插槽,这样就可以处理您感兴趣的信号。
在这里插入图片描述
信号和插槽机制是类型安全的:信号的签名必须与接收槽的签名匹配。(实际上,一个slot的签名可能比它接收到的信号短,因为它可以忽略额外的参数。)由于签名是兼容的,所以当使用基于函数指针的语法时,编译器可以帮助我们检测类型不匹配。基于字符串的SIGNAL和SLOT语法将在运行时检测类型不匹配。信号和槽是松耦合的:发出信号的类既不知道也不关心哪个槽接收到信号。Qt的信号和槽机制确保如果你将一个信号连接到一个槽,槽将在正确的时间用信号的参数调用。信号和槽可以接受任意数量的任何类型的参数。它们是完全类型安全的。
所有从QObject或其子类(例如,QWidget)继承的类都可以包含信号和槽。当对象以其他对象可能感兴趣的方式改变其状态时,就会发出信号。这就是对象所做的所有通信。它不知道也不关心是否有东西在接收它发出的信号。这是真正的信息封装,并确保对象可以用作软件组件。
插槽可以用来接收信号,但也是普通的成员函数。就像一个对象不知道是否有任何东西接收到它的信号一样,一个槽也不知道是否有任何信号连接到它上面。这确保了真正独立的组件可以用Qt创建。
您可以将任意数量的信号连接到一个插槽,并且一个信号可以连接到任意数量的插槽。甚至可以将一个信号直接连接到另一个信号。(这将在第一个信号发出时立即发出第二个信号。)
信号和插槽一起构成了一个强大的组件编程机制。

信号

当对象的内部状态以某种方式发生变化,对象的客户端或所有者可能会感兴趣时,对象就会发出信号。信号是公共访问函数,可以从任何地方发出,但我们建议只从定义信号及其子类的类发出信号。
当发出信号时,连接到它的槽通常立即执行,就像普通的函数调用一样。当这种情况发生时,信号和槽机制完全独立于任何GUI事件循环。一旦所有槽都返回,emit语句之后的代码就会执行。当使用排队连接时,情况略有不同;在这种情况下,emit关键字后面的代码将立即继续执行,而槽将稍后执行。
如果多个槽连接到一个信号,则在信号发出时,这些槽将按照它们连接的顺序依次执行。
信号是由moc自动生成的,不能在.cpp文件中实现。它们永远不能有返回类型(即使用void)
关于参数的注意事项:我们的经验表明,如果信号和槽不使用特殊类型,它们的可重用性会更好。如果QScrollBar::valueChanged()使用一个特殊类型,比如假设的QScrollBar::Range,那么它只能连接到专门为QScrollBar设计的槽。将不同的输入部件连接在一起是不可能的。

当一个连接到槽的信号被发出时,就会调用这个槽。Slots是普通的c++函数,可以正常调用;它们唯一的特殊之处在于信号可以与它们相连。
由于slots是普通的成员函数,因此在直接调用时遵循普通的c++规则。然而,作为槽,它们可以被任何组件调用,而不管其访问级别如何,都可以通过信号槽连接调用。这意味着从任意类的实例发出的信号可能导致在不相关类的实例中调用私有槽。
还可以将槽定义为虚拟的,我们发现这在实践中非常有用。
与回调相比,信号和槽稍微慢一些,因为它们提供了更大的灵活性,尽管对于实际应用程序来说差异并不大。一般来说,发出连接到某些槽的信号比直接调用接收器(使用非虚拟函数调用)慢大约10倍。这是定位连接对象、安全地遍历所有连接(即检查后续接收器在发射期间没有被销毁)以及以通用方式编组任何参数所需的开销。虽然10个非虚函数调用听起来很多,但它的开销比任何new或delete操作都要少得多。只要在后台执行需要new或delete的字符串、向量或列表操作,信号和槽开销只占整个函数调用成本的很小一部分。当在槽中执行系统调用时也是如此;或者间接调用十多个函数。信号和槽机制的简单性和灵活性是值得的,用户甚至不会注意到这些开销。
请注意,在与基于qt的应用程序一起编译时,定义称为信号或槽的变量的其他库可能会导致编译器警告和错误。要解决这个问题,请#undef有问题的预处理器符号。

示例

一个c++类声明可能是:

  class Counter{public:Counter() { m_value = 0; }int value() const { return m_value; }void setValue(int value);private:int m_value;};

一个基于qobject的类可能是这样的:

  #include <QObject>class Counter : public QObject{Q_OBJECTpublic:Counter() { m_value = 0; }int value() const { return m_value; }public slots:void setValue(int value);signals:void valueChanged(int newValue);private:int m_value;};

基于qobject的版本具有相同的内部状态,并提供访问状态的公共方法,但除此之外,它还支持使用信号和插槽进行组件编程。这个类可以通过发出一个信号valueChanged()来告诉外界它的状态发生了变化,并且它有一个槽,其他对象可以向它发送信号。
所有包含信号或槽的类必须在声明的顶部提到Q_OBJECT。它们还必须(直接或间接)派生自QObject。
槽由应用程序程序员实现。下面是Counter::setValue()槽的一种可能实现:

  void Counter::setValue(int value){if (value != m_value) {m_value = value;emit valueChanged(value);}}

调用:

      Counter a, b;QObject::connect(&a, &Counter::valueChanged,&b, &Counter::setValue);a.setValue(12);     // a.value() == 12, b.value() == 12b.setValue(48);     // a.value() == 12, b.value() == 48

调用a.setValue(12)使a发出一个valueChanged(12)信号,b将在其setValue()槽中接收该信号,即调用b.setValue(12)。然后b发出相同的valueChanged()信号,但由于没有插槽连接到b的valueChanged()信号,因此该信号被忽略。
注意,setValue()函数只在value != m_value时设置值并发出信号。这可以防止循环连接情况下的无限循环(例如,如果b.valueChanged()连接到a.setValue())。
默认情况下,每建立一个连接,都会发出一个信号;对于重复的连接发出两个信号。您可以通过一个disconnect()调用中断所有这些连接。如果您传递Qt::UniqueConnection类型,则只有在它不是重复的情况下才会进行连接。如果已经有一个重复的信号(相同对象上相同插槽的相同信号),连接将失败,connect将返回false
这个例子说明了对象可以一起工作,而不需要知道彼此的任何信息。要启用这一点,只需要将对象连接在一起,这可以通过一些简单的QObject::connect()函数调用或ic的自动连接特性来实现。

信号和槽高级用法

【Qt之信号和槽】对象多层嵌套后,高效使用信号和槽链接: https://blog.csdn.net/MrHHHHHH/article/details/133755793

信号和槽注意

使用信号和槽,需要注意:

  • 需要继承QObject或其子类
  • 在类声明的最开始处添加Q_OBJECT
  • 信号和槽参数需保持一致,且槽参数不能多于信号参数
  • 信号只可声明,没有过定义,返回值为void类型

信号和槽连接及取消连接

以下方式是线程安全的:

- connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
- connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type)
- connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type)
- connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
- connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type)
- disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
- disconnect(const char *signal, const QObject *receiver, const char *method)
- disconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method) 

带有默认参数的信号和槽

信号和槽的签名可能包含参数,参数可以有默认值。考虑QObject::destroyed():

void destroy (QObject* = 0);

当一个QObject被删除时,它会发出这个QObject::destroyed()信号。我们希望在任何可能有对已删除的QObject的悬空引用的地方捕获这个信号,这样我们就可以清除它。合适的插槽签名可能是:

void objectDestroyed(QObject* obj = 0);

为了将信号连接到插槽,我们使用QObject::connect()。有几种方法连接信号和插槽。第一种是使用函数指针:

connect(sender, &QObject::destroyed, this&MyObject::objectDestroyed);

对函数指针使用QObject::connect()有几个优点。首先,它允许编译器检查信号的参数是否与槽的参数兼容。如果需要,参数也可以由编译器隐式转换。
也可以连接到函数函数或c++ 11 lambda:

connect(sender, &QObject::destroyed, [=](){this->m_objects.remove(sender);};

将信号连接到槽的另一种方法是使用QObject::connect()以及signal和slot宏。关于是否在SIGNAL()和SLOT()宏中包含参数(如果参数具有默认值)的规则是,传递给SIGNAL()宏的签名必须不少于传递给SLOT()宏的签名的参数。
所有这些都行得通:

connect(sender, SIGNAL(destroyed(QObject*))this, SLOT(objectDestroyed(qbobject *)));
connect(sender, SIGNAL(destroyed(QObject*))this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed())this, SLOT(objectDestroyed()));

但这个行不通:

connect(sender, SIGNAL(destroyed())this, SLOT(objectDestroyed(QObject*)));

此连接将报告运行时错误。
注意,当使用QObject::connect()重载时,编译器不会检查信号和槽参数。

信号和槽的自动关联

在设计师界面,添加按钮,右键转到槽,
在这里插入图片描述
会生成由 on + 部件名称 + 信号名称 3部分组成的槽函数,如:

private slots:void on_pushButton_clicked();

信号和槽断开连接

disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
断开有以下几种方式:

  1. 断开与一个对象所有信号的连接:
disconnect(obj, 0, 0, 0);

等价于

obj->disconnect();
  1. 断开与一个指定信号的所有连接:
disconnect(obj, SIGNAL(sig()), 0, 0);

等价于

obj->disconnect(SIGNAL(sig()));
  1. 断开与一个置顶接收者所有连接:
disconnect(obj, 0, rev, 0);

等价于

obj->disconnect(rev);
  1. 断开一个指定信号和槽连接
disconnect(obj, SIGNAL(sig()), rev, SLOT(st()));

等价于

obj->disconnect(obj, SIGNAL(sig()), rev, SLOT(st()));

还等价于

disconnect(conRes); // conRes是进行连接connect()时返回值

信号和槽高级应用

对于可能需要信号发送方信息的情况,Qt提供了QObject::sender()函数,它返回一个指向发送信号的对象的指针。
QSignalMapper类是为这样的情况提供的:许多信号连接到同一个槽,而槽需要以不同的方式处理每个信号。
假设您有三个按钮决定打开哪个文件:“税务文件”、“帐户文件”或“报告文件”。
为了打开正确的文件,使用QSignalMapper::setMapping()将所有QPushButton::clicked()信号映射到QSignalMapper对象。然后将文件的QPushButton::clicked()信号连接到QSignalMapper::map()槽。

      signalMapper = new QSignalMapper(this);signalMapper->setMapping(taxFileButton, QString("taxfile.txt"));signalMapper->setMapping(accountFileButton, QString("accountsfile.txt"));signalMapper->setMapping(reportFileButton, QString("reportfile.txt"));connect(taxFileButton, &QPushButton::clicked,signalMapper, &QSignalMapper::map);connect(accountFileButton, &QPushButton::clicked,signalMapper, &QSignalMapper::map);connect(reportFileButton, &QPushButton::clicked,signalMapper, &QSignalMapper::map);

然后,将mapped()信号连接到readFile(),根据按下的按钮打开不同的文件。

      connect(signalMapper, SIGNAL(mapped(QString)),this, SLOT(readFile(QString)));

结论

从哪里跌倒就在哪里躺会儿

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

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

相关文章

基于Go语言实现简易Web应用

目录 前言Go语言特点写在使用Go语言实现Web应用前面创建Web服务器声明一个结构体操作加入中间件的使用使用静态文件服务器最后 前言 在编程语言中&#xff0c;近几年问世的几个新语言都是非常不错的&#xff0c;比如Go、Python、 Rust等等。其中&#xff0c;Go语言(Golang)作…

CookieSession Redis 到JWT会话管理历史

单应用时期&#xff0c;通常使用 Cookies 和 Session 进行会话管理。 用户登录后&#xff0c;服务器创建一个唯一的会话标识符&#xff08;Session ID&#xff09;&#xff0c;将其存储在浏览器的 Cookies 中&#xff0c;并在服务端维护一个关联该标识符的会话对象。 这种方…

全国停车位收费标准接口API

1) 请求地址 接口地址https://psbg.jparking.cn/cw-gateway/cwzg/v1/near_park 2) 调用方式&#xff1a;HTTP post 3) 接口描述&#xff1a; 数据来源捷停车 不可用于商用 概不负责 4) 请求参数: {"latitude": "29.563009", //坐标"longitude&quo…

Verilog基础:编译指令`timescale

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 timescale编译指令用于指定指令后模块的时间单位和时间精度。时间单位是时间值的度量单位&#xff0c;例如延迟值和仿真时间&#xff1b;而仿真精度决定了最小可分…

集成测试如何做?

今天学习下如何进行集成测试。 什么是集成测试? 集成测试被定义为一种测试类型&#xff0c;其中软件模块在逻辑上集成并作为一个组进行测试。一个典型的软件项目由多个软件模块组成&#xff0c;由不同的程序员编码。此级别测试的目的是在集成这些软件模块时&#xff0c;暴露…

全网最新最全的Appium自动化:Appium常用操作之H5页面操作 --待补充!

手机chrome浏览器操作&#xff1a; 手机端chrome浏览器一般用于打开H5手机版网站&#xff0c;它的操作方式与PC端的浏览器操作&#xff08;即selenium对浏览器的操作&#xff09;是一模一样的&#xff0c;由于切换后的WebView页面也属于网页 下述的方法中部分支持在webview页面…

什么是JVM的内存模型?详细阐述Java中局部变量、常量、类名等信息在JVM中的存储位置

导航&#xff1a; 【Java笔记踩坑汇总】Java基础JavaWebSSMSpringBootSpringCloud瑞吉外卖/黑马旅游/谷粒商城/学成在线设计模式面试题汇总性能调优/架构设计源码-CSDN博客 目录 一、JVM基本介绍 二、JVM内存模型 2.0 概述 2.1 类加载子系统 2.2 运行时数据区 2.2.0 基本…

python中的输入输出

文章目录 输入函数input()例子1.如何输入获得两个字符串?&#xff08;若输入abc def或abc,def)2.如何输入获得两个整数?&#xff08;若输入34,567)3.如何输入后获得一个元素均为数值型的列表?&#xff08;若输入12,3.4,567或[12,3.4,567]&#xff09; 输出输出函数print()pr…

【上海大学《面向对象程序设计A》课程小项目报告】抽象向量类模板及其派生类

1 项目内容及要求 本项目通过设计一个抽象向量类模板&#xff0c;以及一个通用的向量类模板和一个字符串类作为其派生类&#xff0c;以满足各种应用场景中的数据存储和处理需求。 项目内容&#xff1a; 抽象向量类模板。派生向量类。派生字符串类。测试及异常处理。联合测试…

如何计算光伏电站的发电量?

光伏电站的发电量是衡量其性能和经济效益的关键指标。准确地预测和计算光伏电站的发电量对于投资决策、系统设计和优化至关重要。以下是一些计算光伏电站发电量的主要步骤和方法&#xff1a; 1、确定光伏电站的规模和配置 了解光伏电站的组件数量、类型、功率等级以及安装位置…

html和css写QQ会员页面导航

目录 1、css代码 2、html代码 效果图 1、css代码 <style>* {padding: 0;margin: 0;list-style: none;text-decoration: none;}div {margin: 30px auto;}li {float: left;height: 60px;background-color: rgb(102, 102, 102);line-height: 40px;}img {height: 100%;ma…

Vellum —— 相关特点

目录 Cloth Breaking and tearing Paneling and draping Cloth simulation Calculating mass and thickness Working with low res and high res cloth Quick moving cloth Softbody Vellum softbodies Plasticity with softbodies Constraints Stitch and slid…

Java集合常见问题

目录 Java集合 1.前言2.集合3.Collection接口类3.1 List接口3.1.1 ArrayList&#xff08;常用&#xff09;3.1.2 LinkedList&#xff08;常用&#xff09;3.1.3 Vector&#xff08;不常用&#xff09; 3.2 Set接口3.2.1 HashSet&#xff08;常用&#xff09;3.2.2 LinkedHash…

【银行测试】第三方支付功能测试点+贷款常问面试题(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、第三方支付功能…

华为快应用中自定义Slider效果

文章目录 一、前言二、实现代码三、参考链接 一、前言 在华为快应用中官方提供了<slider>控件&#xff0c;但是这个控件的限制比较多&#xff0c;比如滑块无法自定义&#xff0c;所以这里进行下自定义&#xff0c;自己修改样式。 二、实现代码 整体效果如下: 源码如下…

SL6015B降压恒流60V耐压1.5A高辉调光LED芯片 电路简单 元器件少

SL6015B是一款专为LED照明应用设计的降压恒流芯片&#xff0c;具有60V的耐压能力&#xff0c;最大输出电流可达1.5A。它采用高辉调光方式&#xff0c;通过改变输入电压或电流来调节LED的亮度。此外&#xff0c;SL6015B还具有电路简单和元器件数量少的特点&#xff0c;使其成为一…

QT-在ui界面中给QWidget增加Layout布局的两种方法

QT-在ui界面中给QWidget增加Layout布局的两种方法 方式一 在UI界面&#xff0c;用拖拽的方式加入Layout方式二 用notepad软件打开.ui文件&#xff0c;手动加入Layout代码 目标&#xff1a;去除右下角红标&#xff0c;给tab标签增加Layout属性。 方式一 在UI界面&#xff0c;用…

leetcode:前序遍历

题目描述 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目分析 这道题的难点在于&#xff0c;前序遍历一遍之后需要将数值存在数组里&#xff0c;returnsize就是数组的大小 所以我们先构建一个函数来计算节点的个数 然后我…

中国AI大模型,应该如何商业化?

虽然大模型商业化的路径较为清晰&#xff0c;目前国内厂商也都在积极探索&#xff0c;但大模型的商业化之路&#xff0c;不能仅限于商业模式的探索尝试&#xff0c;更在于解决大模型发展的底层问题。 作者|斗斗 编辑|皮爷 出品|产业家 如今&#xff0c;大模型的商业化问题再…

深度观察|近视防控乱象下,角膜塑形镜撬动百亿市场的背后…

前不久&#xff0c;“晒眼皮”莫名上了热搜。顾名思义&#xff0c;就是在太阳底下闭眼&#xff0c;让眼皮享受日光浴。 不少近视患者相信&#xff0c;这种做法可以延缓近视&#xff0c;甚至可以让近视度数“清零”。毫无疑问&#xff0c;这是一个收割智商税的做法&#xff0c;…