Qt :信号与槽

信号与槽

  • 信号介绍
  • connect 函数使用
    • connect 函数传参问题
  • 定义槽(solt)函数
    • 方法一
    • 方法二
  • 定义信号
    • 关键字 signals、emit
  • 定义带参数的信号和槽
    • 参数个数不一致问题
    • 断开信号和槽的连接 disconnect
  • lambda 表达式

信号介绍

Qt 中,信号会涉及三个要素:

  • 信号源:每一个控件,都会独属于自己的信号,这个信号源通常由控件发出

  • 信号的类型:用户进行不同的操作,就会触发不同的信号
    例如:点击一个按钮,会触发点击的信号;在输入框中移动光标,就会触发光标移动的信号;选择下拉框,也会触发不同的信号。

  • 信号的处理方式:槽(slot),槽是一个回调函数,用于处理控件发出的信号

connect 函数使用

Qt 中,一般使用 connect 函数,把信号和槽关联起来。当使用了这个函数,信号被触发后会自动去调用执行槽函数

前提是,槽函数要先实现了才能处理控件发出的信号。顺序不能颠倒,单纯有信号没有信号的处理方式是办不了事的!

学 Linux 老铁,不要将这里提到的 connect 函数和 Linux 的 TCP socket 中 connect 建立链接的函数弄混淆了。它们两个之间没有联系,没有联系,没有联系!只是名字恰好相同而已。

Qt 中 connect函数 是 QObject类提供的一个静态成员函数。

Qt 中很多的类,都是存在一定的继承关系
例如,我们悉知的 Qwidget 这个类的父类就是 QObject。而 Qwidget 类又是诸多控件的父类,QPushButton、QLineEdit等等都是 Qwidget 的子类。可以说 QObject 就是QT中内置类中的祖宗类!因为这些内置类都会间接去继承 QOject 类。

因此,在任何类中都可以直接使用 connect 这个函数

语法:

connect(const QObject* sender, 
		const char* signal, 
		const QObject* receiver, 
		const char* method, 
		Qt::ConnectionType type = Qt::AutoConnection )

看到这个函数的参数,我相信会劝退很多人。其实不然,这个函数没有想象的很难使用。

最常用的只是前 4个参数,最后一个参数很少用到。

  • sender 参数:传入的信号是哪个控件发出来的
  • signal 参数:传入这个信号的类型(点击、键盘输入、拖动鼠标… ),信号也是控件的成员
  • receiver 参数:传入负责处理信号的控件
  • method 参数:传入负责处理信号的控件的内部实现的成员函数

使用 connect 函数的时候,联想一下 Qt 中的信号三要素,再加上处理信号的控件就很容易记下来了。

下面来举个示例:

实现一个功能按钮,当用户点击这个按钮时,将整个窗口都关闭

  1. 在 Widget 构造函数内部,实例化一个 QPushButton 对象(记得包含头文件!)设置这个按钮的文本,并且移动到特定位置:
    在这里插入图片描述
    实现效果如下:
    在这里插入图片描述
    当然现在点击这个按钮是没有任何反应的,没有编写 connect 函数去处理信号对应的效果。
  2. 调用 conncet 函数,实现点击按钮关闭整个窗口的功能。
    在这里插入图片描述
    实现效果如下:
    在这里插入图片描述

connect 函数传参问题

下面来说一下,connect 函数的两个参数,分别是:

  1. const char* singal;
  2. const char* method;

上面代码中,使用这两个参数时,我们是这样传参的:

connect(mybutton, &QPushButton::clicked, this, &Widget::close);

传参时,针对函数取地址得到的是一个函数指针! 但是,connect 函数参数所能接收的是 char* 类型的指针。

在 C/C++ 中,指针也是分类型的。例如:int*char*float*… 在这里对 QPushButton::clicked 和 对Widget::close 函数取地址,应该分别用 void (*)() 类型指针 和 bool (*)() 类型指针来接收。

上面提到的 connect 函数,这不指针类型不匹配吗?在C++中,是不允许使用两个不同类型指针相互赋值的!但是,Qt 中却没有报错。

其实上面提到的 connect 函数的声明是很老旧版本的,没想到吧。

以前在使用老版本的 connect 函数时,是需要对这两个传入的参数搭配两个宏来使用的

  1. 信号参数传参要搭配:SIGNAL
  2. 槽函数传参需要搭配:SLOT

拿上面代码举例:

connect(mybutton, SIGNAL( &QPushButton::clicked), this, SLOT(&Widget::close));

使用这两个宏,会将取到的函数指针类型转换成 char* 类型的指针

上面 connect 函数写法在 Qt 5版本后就不再使用了。本身 connect 函数的参数又多,在加上这两个宏,实在是太繁琐。

Qt 5 版本后,connect 函数提供了一个重载版本。针对第二个参数和第四个参数提供了泛型参数,允许传入任何的指针类型

重载版本的 connect 函数声明如下:

template<typename Func1, typename Func2> //Func1 和 Func2 是泛型参数
static inline QMetaObject::Connection connect(
const typename QtPrivate::FuncitionPointer<Funcl>::Object* sender, 
Func1 signal, 
const typename QtPrivate::FunctionPointer<Func2>:: Object* receiver, 
Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection)
)

使用了新版的 connect 函数重载后,connect 函数就带有了一定的 参数检查 功能。

如果传入的第一个参数和第二个参数不匹配(这里的不匹配是指:参数二的指针不是参数一的成员函数)第三个参数和第四个参数不匹配,此时就会编译出错!

定义槽(solt)函数

定义槽函数在开发中是非常重要的,所谓的槽函数其实是一个普通的函数,和在类中定义一个成员函数没有多大的区别,槽函数主要用于处理接收信号。当用户触发到某个操作时,要进行的业务逻辑。

自定义槽函数的方式有两种。

下面举个例子,实现一个按钮控件,当按下后更改窗口的标题:

方法一

  • 实例化一个控件对象,手动在 Widget 类中定义槽函数,通过调用 connect 函数来关联信号和槽;
  1. 实例化一个 QPushButton 对象,设置这个按钮的文本,并且移动到特定位置:
    在这里插入图片描述
    在这里插入图片描述
  2. 在QWidget 类中编写 headleClicked 槽函数;调用 connect 函数,将mybutton 对象发出的信号 和 headleClicked 槽函数关联起来。实现对应的功能:
    在这里插入图片描述
    实现效果如下:
    在这里插入图片描述

方法二

  • 通过 ui 文件,直接编写槽函数
  1. 找到 widget.ui 文件,双击 widget.ui 文件,跳转到可视化界面:
    在这里插入图片描述
    在这里插入图片描述
  2. 找到 Buttons 模块下的 Push Button 控件,拖拽到右图的可视化编辑页面,编辑和调整按钮的文本和大小:
    在这里插入图片描述
  3. 鼠标右击刚刚拖拽的按钮控件,选择转到槽函数:
    在这里插入图片描述
    此时,会出现关于这个控件的所有信号选项。这个时候,选择我们需要实现功能的信号即可:
    在这里插入图片描述
  4. 点击 ok 选项后,会直接跳转到槽函数实现上。此时,只需要对槽函数进行编写功能即可:
    在这里插入图片描述
  5. 上述操作都做完后,我们可以直接编译程序,生成我们想要的效果:
    在这里插入图片描述

可以看到,在使用第二种方法是比较方便的,并不需要用户直接去定义槽函数的函数声明 和 函数名,只需要编写对应的功能即可。更甚至可以不用直接去调用 connect 函数来关联 信号 和 槽函数。

问题来了,没有调用 connect 函数,那么信号和槽是怎么关联的?

此时,就要谈一下方法二直接形成的槽函数的名字了:

void Widget::on_pushButton_clicked();

这个槽函数的名字是系统直接默认生成的!仔细观察这个函数名字会发现一些规律。

on_pushButton_clicked 由以下几部分组成:

on 前缀、pushButton 是按钮控件的 objectName、clicked是这个按钮控件的信号

在这里插入图片描述

当槽函数名称都符合这样的排序规则,Qt 就会自动将信号和槽函数给建立联系。

ui_widget.h 文件中的 setupUi成员函数内部会调用 connectSlotsByName 这个函数,这个函数就会根据上面提到的槽函数命名规则,自动去关联对应控件的信号
在这里插入图片描述

但是,如果没有按照对应要求去实现对应的名称,Qt 就关联不上对应的信号和槽。一般让编译器实现就行,并不需要我们刻意去关心。

  • 如果通过图形界面创建控件,那么推荐使用第二种方法来实现槽函数,从而达到快速链接信号和槽
  • 如果使用代码的方式创建控件,那么就要像方法一那般实现槽函数后,再调用 connect 函数链接信号和槽

定义信号

Qt 中是允许自定义信号的,信号对应的是用户的操作。

信号是一个特殊的函数。与普通函数和槽函数不同,我们只需要写出函数声明,并且告诉编译器这是一个信号即可

编译过中编译器会自行生成信号的定义,我们不做干预,也干预不了。

定义信号需要注意以下几点:

1. 信号是个特殊的函数,这个函数不需要手动定义内容,编译器会自动完成
2. 信号不需要返回值,直接设置为 void
3. 信号也没有参数都可以,也支持函数重载

关键字 signals、emit

定义一个信号需要用到一个 Qt 内置的关键字:signals。这个关键字不是 C++ 标准,是Qt自己扩展出来的。

当编译器进行编译的时候,如果扫描到 signals 关键字,就会将关键字下面的函数声明识别为信号,并且对这些函数自动生成函数定义。

与 Qt 内置信号不同的是,我们自定义的信号需要被触发的才能发送信号。触发信号需要用到关键字:emit

下面举个示例:

  1. 在 Widget 类中,定义 mysignal 信号、定义触发 mysignal 信号所实现的槽函数
    在这里插入图片描述
    在这里插入图片描述
  2. 在GUI 界面拖拽一个 PushButton 控件,当按下按钮,按钮控件发射信号调用槽函数,再通过槽函数内部发射 mysignal 信号,再去调用 headleMysignal 槽函数:
    在这里插入图片描述
    由于是 ui 文件拖拽的控件实现的槽函数,因此 pushbutton 不用手动调用 connect 函数。在这里只需要链接widget发射的信号和处理这个信号的 headleMysignal 槽函数即可:
    在这里插入图片描述
    效果如下:
    在这里插入图片描述

定义带参数的信号和槽

定义信号和槽函数时,是可以带参数的。

  • 当信号带有参数的时候,使用槽的参数必须和信号的参数一致

当然,这里指代的一致性,是信号和槽参数类型的一致。当信号和槽函数的参数个数不一致的时候,尽量要保证信号的参数的个数要多于槽函数的参数个数!

用户在发射一个信号时,就是给信号传参的时候。与之对应的的参数就会被传递到槽函数中,这样就达到了让信号给槽函数传参的效果。

举个示例:

  1. 在 Widget 类中定义 mysign 信号和 headleMySignal 槽函数的声明,设置相同的参数类型和参数的个数:
    在这里插入图片描述
    槽函数实现如下:

在这里插入图片描述

  1. 在GUI 界面拖拽一个 PushButton 控件,当按下按钮,按钮控件发射信号调用槽函数,再通过槽函数内部发射 mysignal 信号(对 mysignal 信号传入实参),再去调用 headleMysignal 槽函数:
    在这里插入图片描述
    由于是 ui 文件拖拽的控件实现的槽函数,因此 pushbutton 不用手动调用 connect 函数。在这里只需要链接widget发射的信号和处理这个信号的 headleMysignal 槽函数即可:
    在这里插入图片描述
    实现的效果如下:
    在这里插入图片描述

看到这里的示例,跟前面定义信号的示例没有什么改变,有种多此一举的感觉。

其实并不然,传参可以提到代码复用的效果!如果有多个逻辑,但是总体逻辑整体都一致,只是数据不同。这个时候传参的效果就会展现出来。

下面再来举个例子:以上述例子为扩展,在 ui 界面拖拽两个按钮。当用户点击不同按钮时,更改窗口的标题内容会有所不同。

  1. 在 ui 文件中拖拽两个按钮,分别实现不同的槽函数:
    在这里插入图片描述
  2. 转到按钮一的槽函数,实现的按钮一槽函数的内容:
    在这里插入图片描述
    在这里插入图片描述
  3. 转到按钮二的槽函数,实现的按钮二槽函数的内容:
    在这里插入图片描述
    在这里插入图片描述
    下面来看实现效果:
    在这里插入图片描述

至此,通过这一套信号槽,搭配不同的参数,就可以设置不同标题的效果。

参数个数不一致问题

前面提到,信号和槽函数的参数类型必要保持一致。这个可以理解,信号的参数要传递给槽,类型不一致编译会直接报错。

至于参数的个数问题就要来探讨一下:

下面,我们拿前面的例子来更改一下代码。将信号参数个数变多,槽函数的参数不变( N :1 )来编译一下代码:

在这里插入图片描述
在这里插入图片描述

编译结果如下:
在这里插入图片描述

下面换一种方式,将信号参数保持不变,槽函数的参数个数设置多个(1:N):

在这里插入图片描述
设置第二个参数,没有去使用:
在这里插入图片描述

在这里插入图片描述

编译结果如下:
在这里插入图片描述

总结:

  • 信号的参数个数超过槽函数的参数个数,代码还是可以被编译通过的
  • 信号的参数个数少于槽函数的参数个数,编译直接报错

为什么信号的参数个数就可以多,槽函数参数只允许与信号相同,甚至是少呢?

这是因为一个槽函数可能被多个信号绑定。信号的参数不确定,但是能保证最少的参数的信号和槽函数的参数是可以对应的。当信号和槽函数的参数不匹配时,槽函数在获取参数时,会按照信号参数的顺序,从左往右依次获取,直到槽函数的最后一个参数都能被获取到对应的值!前提是,信号的参数个数多于槽函数参数的个数。

如果,槽函数的参数个数都多于信号的话,就不能保证槽函数的参数都能获取到对应的值。这也是为什么,信号的参数个数可以比槽函数的参数多,反过来就不行。

在这里还要注意一个点:
在 QT 中,如果想要某个类能够使用信号和槽(在类中定义信号和槽函数),在类的最开始部分就要包含一个宏:Q_OBJECT

这个宏展开会生成很多属于 Qt 内部的代码

在这里插入图片描述

如果类中没有加上 Q_OBJECT 这个宏,在使用信号和槽时,会报错!

断开信号和槽的连接 disconnect

disconnect 用法和 connect 类似。由于在Qt中,信号和槽的关系是多对多的。一旦一个信号绑定上一个槽函数后,每次触发这个信号都会调用这个槽函数。如果不去断开连接,下次再用这个信号去绑定其他的槽函数时,触发这个信号,就会调用两个槽函数。

lambda 表达式

lambda 本质是一个匿名函数,主要运用在 回调函数 中。lambda 表达式通常用来创建临时的槽函数。

语法:

[]()
{
	//...
}

匿名函数的生命周期很短,创建使用后就被销毁

与 java 语言不同。在C++中,lambda 表达式是无法直接获取上层作用域中的变量的。为了解决作用域的问题,C++ 引入了 变量捕获 的语法,就是 lambda 表达式中想要用到哪些变量直接引用到 中括号 即可。

下面来举个例子:

创建一个按钮控件,当用户点击按钮时,更改按钮的位置和窗口标题内容。利用 lambda 表达式实现:

  1. 创建按钮控件,设置控件对应的位置:
    在这里插入图片描述
  1. 将 lambda 表达式代替为槽函数,由于要更改按钮控件的位置 和 窗口标题的内容,因此需要将 button 变量 和 this 传给lambda表达式:
    在这里插入图片描述
  2. 实现效果如下:
    在这里插入图片描述

上面传参的方式可以得到很好的解决,但是,当参数很多的时候就会变得很麻烦。

下面来介绍第二种传变量的方式:

[=]() //等号的意义:将上层作用域的所有变量都传给lambda表达式
{
	//...
}    

将上面代码稍作修改:在这里插入图片描述
实现效果如下:
在这里插入图片描述

看到这里就有小伙伴说了,什么时候用 lambda 表达式呢?

当槽函数比较简单且是一次性使用时,我们就可以将槽函数写成 lambda表达式

使用 lambda 表达式需要注意的一点就是变量的生命周期,一般创建控件都是 new 出来的,也就是堆区开辟。但是,不妨有一些控件在使用前就被销毁,因此,在传递变量给lambda表达式时,需要注意变量的生命周期!!!

lambda 表达式是 C++11 标准提出来的,如果 QT 版本低于 QT5 在使用 lambda表达式时会直接编译报错。

如果遇到使用 lambda 表达式出错的,可以在 .pro 文件中 添加这么一句代码 :CONFIG += c++11,就可以解决报错问题。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

装饰器模式-原理分析以及动手练习

目录 应用场景涉及的角色和类&#xff08;个人理解&#xff09;涉及的角色组件&#xff08;标准&#xff09;基本实现 Demo&#xff08;可以直接 copy 跑一下看效果&#xff09;自己动手实战需求参考答案 相关话题参考文章 应用场景 需要给一个现有类添加附加功能&#xff0c;…

北京车展现场体验商汤DriveAGI自动驾驶大模型展现认知驱动新境界

在2024年北京国际汽车展的舞台上&#xff0c;众多国产车型纷纷亮相&#xff0c;各自展示着独特的魅力。其中&#xff0c;小米SUV7以其精美的外观设计和宽敞的车内空间&#xff0c;吸引了无数目光&#xff0c;成为本届车展上当之无愧的明星。然而&#xff0c;车辆的魅力并不仅限…

数据库系统理论——绪论

文章目录 前言一、数据库四个基本概念1、数据2、数据库3、数据库管理系统&#xff08;DBMS&#xff09;4、数据库系统&#xff08;DBS&#xff09; 二、数据模型1、概念数据模型2、逻辑数据模型3、物理数据模型 三、三级模式1、图片解析2、二级映像 前言 最近很长时间没更新学…

皮秒激光切割机可以切割材料及主要应用行业

皮秒激光切割机可以切割多种材料&#xff0c;主要应用行业包括但不限于&#xff1a; 1. PCB板行业&#xff1a;主要用于PCB激光分板&#xff0c;如FR4、补强钢片、FPC、软硬结合板、玻纤板等材料的紫外激光切割。 2. 薄膜材料切割&#xff1a;皮秒紫外激光切割机可以直接切割薄…

无法添加以供审核,提交以供审核时遇到意外错误。如果问题仍然存在,请联系我们

遇到问题&#xff1a; 无法添加以供审核 要开始审核流程&#xff0c;必须提供以下项目&#xff1a; 提交以供审核时遇到意外错误。如果问题仍然存在&#xff0c;请联系我们。 解决办法&#xff1a; 修改备案号为小写&#xff0c; 例如&#xff1a;京ICP备2023013223号-2A 改…

选择了软件测试,你后悔吗?

记得在求职的时候&#xff0c;面试官经常问我&#xff1a;“为什么要选择软件测试工作?”而我也会经常说一堆自己有的没的优势去应付。 工作这么久了&#xff0c;也不再浮躁&#xff0c;静下心来回忆当初选择软件测试工作的历程&#xff0c;也是对自己职业生涯的一次回顾。 下…

初始Linux(基础命令)

前言&#xff1a; 我们不能总沉浸在编程语言中&#xff0c;虽然代码能力提升了&#xff0c;但是也只是开胃小菜。我们要朝着更高的方向发展。 最近小编一直在刷力扣&#xff0c;以至于博客更新的比较少。今天就带各位开始学习全新的知识——Linux.至于为啥要学&#xff1f; Lin…

[正则表达式]正则表达式语法与运用(Regular Expression, Regex)

0. 在线工具 RegExr: Learn, Build, & Test RegEx 1. 场景列举 vim Linux命令行 sublime 编辑器 java、python等语言中 ... ... 不同场景、不同版本语法可能不一样 2. 以下示例数据与基本语法 &2024 &As20242024# 2024sA#abdcefgha_bdcefghABASDSADAASDASD…

MySQL之聚合函数与应用

1. 前言 上文我们讲到了单行函数.实际上SQL还有一类叫做聚合函数, 它是对一组数组进行汇总的函数, 输入的是一组数据的集合, 输出的是单个值. 2. 聚合函数 用于处理一组数据, 并对一组数据返回一个值. 有如下几种聚合函数 : AVG(), SUM(), MAX(), MIN(), COUNT(). 3. AVG(…

[蓝桥杯]真题讲解:班级活动(贪心)

[蓝桥杯]真题讲解&#xff1a;班级活动&#xff08;贪心&#xff09; 一、视频讲解二、正解代码1、C2、python33、Java 一、视频讲解 [蓝桥杯]真题讲解&#xff1a;班级活动&#xff08;贪心&#xff09; 二、正解代码 1、C #include<bits/stdc.h> using namespace st…

28.leetcode---前K个高频单词(Java版)

题目链接: https://leetcode.cn/problems/top-k-frequent-words/description/ 题解: 代码: 测试:

Offline:IQL

ICLR 2022 Poster Intro 部分离线强化学习的对价值函数采用的是最小化均方bellman误差。而其中误差源自单步的TD误差。TD误差中对target Q的计算需要选取一个max的动作&#xff0c;这就容易导致采取了OOD的数据。因此&#xff0c;IQL取消max,&#xff0c;通过一个期望回归算子…

QT creator qt6.0 使用msvc2019 64bit编译报错

qt creator qt6.0报错&#xff1a; D:\Qt6\6.3.0\msvc2019_64\include\QtCore\qglobal.h:123: error: C1189: #error: "Qt requires a C17 compiler, and a suitable value for __cplusplus. On MSVC, you must pass the /Zc:__cplusplus option to the compiler."…

PXE批量网络装机和Kickstart无人值守安装

一、PXE定义 PXE&#xff08;preboot execute environment&#xff09;:用于通过网络来引导系统的标准&#xff0c;工作在Client/Server模式&#xff08;也称为CS模式&#xff09;&#xff0c;允许客户机通过网络从远程服务器上下载引导镜像&#xff0c;并加载安装文件或整个操…

劝退计算机?CS再过几年会没落!?

事实上&#xff0c;未来计算机不仅不会没落&#xff0c;国家还会大力发展 只不过大家认为的计算机就是什么Java web&#xff0c;真正的计算机行业是老美那样的&#xff0c;涉及到方方面面&#xff0c;比如&#xff1a; web&#xff0c;图形学&#xff0c;Linux系统开发&#…

酷得智能电子方案 早教学习机

早教学习机是用户友好的&#xff0c;易于操作&#xff0c;同时要确保内容的科学性和适宜性&#xff0c;以促进儿童的健康成长和智力发展。 通常包括以下几个方面&#xff1a; 1.年龄分级内容&#xff1a;软件会根据儿童的不同年龄段提供相应的教育内容&#xff0c;从新生儿到…

renren-fast开源快速开发代码生成器

简介 renrenfast框架介绍 renren-fast是一个轻量级的Spring Boot快速开发平台&#xff0c;能快速开发项目并交付.完善的XSS防范及脚本过滤&#xff0c;彻底杜绝XSS攻击实现前后端分离&#xff0c;通过token进行数据交互 使用流程 项目地址 https://gitee.com/renrenio/ren…

鸿蒙 DevEcoStudio:组件实例(页面及组件生命周期函数)

【使用onPageshow等生命周期函数】 在entry/src/main/ets/pages路径下创建Page1.ets: import router from ohos.router Entry Component struct Page1 {State message: string Hello WorldState show: booleantrueaboutToAppear(){console.log(Page1组件创建实例)}aboutToDisa…

夏天旅行,就认准这五款随身WiFi!准没错!2024随身wifi靠谱品牌推荐,高性价比高口碑随身wifi推荐

过了五一&#xff0c;气温逐渐上升&#xff0c;又到了最适合旅行的季节。这个时候一款趁手的随身WiFi当然是必不可少的&#xff01;不但能解决出行时信号差的烦恼&#xff0c;还可以解决流量不够用的问题。那么&#xff0c;都有哪些随身WiFi在夏季出行时最值得选择呢&#xff1…

docker容器安装sqlserver

docker容器安装sqlserver 搜索SQL Server镜像下载SQL Server镜像创建容器 搜索SQL Server镜像 docker search mssql-server下载SQL Server镜像 docker pull microsoft/mssql-server-linux创建容器 docker run -e ACCEPT_EULAY -e SA_PASSWORD<YourStrong!Passw0rd> -…