Qt --- 信号和信号槽

news2024/11/16 1:17:06

前言

Linux信号Signal,系统内部的通知机制,进程间通信方式。

信号源:谁发的信号。

信号的类型:哪种类别的信号。

信号的处理方式:注册信号处理函数,在信号被触发的时候自动调用执行。

Qt中的信号和Linux中的信号,虽然不一样,也有很多的相似之处。

Qt中谈到的信号,也是涉及到三个要素。

信号源:由哪个控件发出的信号。

信号的类型:用户进行不同的操作,就可能触发不同的信号。比如点击按钮信号。在输入框中移动光标,触发移动光标的信号,勾选一个复选框,选择一个下拉框,都会触发出不同的信号。我们写的GUI程序,就是要让用户进行操作,就是要和用户进行交互,这个过程中就需要关注,用户当前的操作具体是个什么样的操作。

信号的处理方式:槽(slot) =》就是一个函数,Qt中可以使用connect函数这样的函数,把一个信号和槽关联起来。后续只要信号触发了,Qt就会自动的执行槽函数,所谓的槽函数本质上也是一中回调函数。

一定是先把信号的处理方式准备好,再触发信号,Qt中,一定是先关联信号和槽,然后再触发这个信号,顺序不能颠倒。否则信号就不知道如何处理了。就错过信号的处理了。

一、Connect函数的用法

connect这个函数和Linux中TCPsocket中建立连接的函数,没有任何关系,只是名字恰好一样罢了。

是QObject提供的静态的成员函数。

Qt中提供的这些类,本身是存在一定的继承关系的。

QObject就是其他Qt内置类的祖宗。唉Java中也存在类似的设定。Java所有的类都是继承自Object类。

connect具体的使用方式

type参数可以先不考虑。

sender 描述了当前信号是哪个控件发出来的。

signal 描述当前信号的类型

receiver和method 信号如何处理。receiver:哪个对象负责处理。method:这个信号该如何处理。

 代码:

#include "widget.h"
#include "ui_widget.h"

#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("按钮");
    //绑定信号和槽函数
    //最后一个槽函数是Widget类里面包含的一个槽函数
    connect(button,&QPushButton::clicked,this,&Widget::close);
    button->move(100,100);
}

Widget::~Widget()
{
    delete ui;
}

界面上包含一个按钮,点击这个按钮就关闭窗口。所谓的信号也是QPushButton中指定的一些成员函数。

click 是一个slot函数,作用就是在调用的时候相当于点击了一下按钮。

clicked 才是要触发的点击信号。connect要求这两参数是匹配的,button的类型如果是QPushButton*,此时,第二个参数的信号必须是QPushButton内置的信号(父类的信号),不能是一个其他的类,比如QLineEdit的信号。close是QWidget内置的槽函数。Widget继承了QWidget所以Widget就可以使用close这个函数了。

两个问题:

1、咋知道QPushButton有个clicked信号?咋知道的QWidget有一个close槽?

多看文档!在翻阅文档的时候,如果在当前类中没有找到对应的线索,不妨去看看这个类的父类。

参数在QPushButton用不到。

查阅文档中的信号的时候,最重点就是关注信号的发送时机(用户进行了啥样的操作,就能够产生这个信号) 

2、char*和函数指针是同一个东西吗?

所谓的指针,其实是一个统称。char*和int*和函数指针都是不同的类型!  void(*)()是该函数的函数指针。bool(*)()即使是这两个函数指针的类型都是不一致的!!C++中,不允许你使用两个不同的指针类型相互赋值。

这个函数声明,是以前旧版本Qt的connect函数的声明。以前版本中传参的写法和现在其实也是有区别的。此时给信号参数传参,需要搭配一个SIGNAL宏。给槽参数传参,需要搭配一个SLOT传入的函数指针转成char*

从Qt 5开始对上述写法做出了简化,不再需要写SIGNAL和SLOT宏了。给connect提供了重载版本。重载版本中,第二个参数和第四个参数成了泛型参数,允许咱们传入任意类型的函数指针了。

第一个 Qt封装的类型萃取器,此时connect函数就带有了一定的参数检查功能。如果你传入的第一个参数和第二个参数不匹配。或者第三个参数和第四个参数不匹配。此时代码编译出错。

 二、自定义信号和自定义槽

所谓的slot就是一个普通的成员函数

代码:按下按钮,修改一下窗口标题 setWindowTitle。

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("更换名字");
    //连接信号和槽函数
    connect(button,&QPushButton::clicked,this,&Widget::handler);
    button->move(100,100);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::handler()
{
    setWindowTitle("改变窗口名字");
}

所谓的自定义一个槽函数,和自定一个普通函数没有什么区别。

在以前版本的Qt中,槽函数必须放到public slots后。

 Qt里广泛使用了元编程技术(基于代码生成代码),qmake构建Qt项目的时候,就会调用专门的扫描器,扫描代码中特定的关键字。基于关键字自动生成一大堆相关的代码。

第二种自定义槽的方式:

//Qt生成的符合规则的函数名,可以被Qt自动连接信号和槽。是在ui_widget.h这个文件里处理的
void Widget::on_pushButton_clicked()
{
    setWindowTitle("修改窗口名字");
}

 点击后会生成一个函数,声明也罗列好了。

现在我们就可ui编写代码了。在Qt中,除了通过connect连接信号槽意外,还可以通过函数名字的方式来自动连接。

当函数名符合上述的规则之后,Qt就能自动的把信号和槽给建立上联系!

正是在自动生成ui_widget.h中调用的。如果我们通过图形化界面创建控件,还是推荐使用这种快速的方式来连接信号槽。如果我们是通过代码的方式来创建控件,还是得手动connect,(你的代码中没有调用 connectSlotByName这样的函数)。

Qt中也允许自定义信号。

自定义槽函数,非常关键,开发大部分情况都是需要自定义槽函数的。槽函数就是用户触发某个操做之后,要进行的业务逻辑。

自定义信号比较少见,实际开发中很少会需要自定义信号。信号就对应到用户的某个操作。在GUI中用户能够进行哪些操作,是可以穷举的,Qt内置的信号,基本上已经覆盖到了。上述所有可能的用户操作。因此,使用Qt内置的信号,就足以应对大部分的开发场景了。自定义信号,本身代码比较简单的。

咱们的Widget虽然还没有定义任何信号,由于继承自QWidget和QObject,这两类里面已经提供了一些信号了,可以直接使用。所谓的Qt的信号,本质上就是一个函数,Qt5 以及更高版本中,槽函数和普通的成员函数之间,没啥差别了。

1、但是,信号则是一类非常特殊的函数,程序员只要写出函数声明,并且告诉Qt,这是一个信号即可,这个函数的定义,是Qt在编译过程中,自动生成的,自动生成的过程,程序员无法干预。信号在Qt中特殊的机制,Qt生成的信号函数的实现,要配合Qt框架做很多既定的操作!!
2、作为信号函数,这个函数的返回值,必须是void。有没有参数都可以,甚至也可以支持重载。

qmake的时候,调用一些代码的分析生成工具。扫描到类中包含signals这个关键字的时候,此时就会自动的把下面的函数声明认为是信号,并且给这些信号函数自动的生成函数定义。

建立连接,不代表信号发出来了,如何才能触发自定义的信号呢?

Qt内置的信号,都不需要咱们手动通过代码来触发,用户在GUI,进行某些操作,就会自动触发对应信号(发射信号的代码已经内置到Qt框架中了)。emit发射信号。emit mySignal(); emit是Qt扩展出来的关键字。其实在Qt 5中emit现在啥都没做。真正的操作都包含在mySignal内部生成的函数定义了。即使不写emit,信号也能发出去!即使如此,实际开发中,还是建议把emit都加上,加上代码可读性更高,更明显的表示出,这里是发射自定义信号。

代码:

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //QPushButton* button = new QPushButton(this);
    //button->setText("按钮");
    connect(this,&Widget::Mysignal,this,&Widget::MysignalHand);
    
}

Widget::~Widget()
{
    delete ui;
}

void Widget::MysignalHand()
{
    setWindowTitle("自定义信号");
}

emit发送信号

信号和槽也可以带参数。当信号带有参数的时候,槽的参数必须和信号的参数一致。此时发射信号的时候,就可以给信号函数传递实参,与之对应的这个参数就会传递到对应的槽函数中。一致主要是要求类型一致,个数如果不一致也可以,不一致的时候,要求信号的参数的个数必须比槽的参数要更多。

传参可以起到复用代码的效果。有多个逻辑,逻辑上整体基本一致,但是涉及到的数据不同,就可以通过函数 - 参数来复用代码,并且在不同的场景中传入不同的参数即可。

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button1 = new QPushButton(this);
    button1->setText("按钮1");
    button1->move(100,100);
    QPushButton* button2 = new QPushButton(this);
    button2->setText("按钮2");
    button2->move(200,200);
    connect(button1,&QPushButton::clicked,this,&Widget::handler1);
    connect(button2,&QPushButton::clicked,this,&Widget::handler2);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::MysignalHand(const QString& text)
{
    setWindowTitle(text);
}

void Widget::handler1()
{
    MysignalHand("传递参数1");
}

void Widget::handler2()
{
    MysignalHand("传递参数2");
}

通过这一套信号槽,搭配不同的参数,就可以起到设置不同标题的效果。Qt中很多内置的信号,也是带有参数的。这些参数不是自己传递的。clicked信号就带有一个参数。

信号函数的参数多于槽函数参数的个数,此时可以正常使用。

信号函数的参数个数,少于槽函数的参数个数,此时代码无法编译通过。一个槽函数,有可能绑定多个信号。如果我们严格要求个数一致,就意味着信号绑定到槽的要求就变高了。换而言之,当下这样的规则,就允许信号和槽之间的绑定更灵活了。更多的信号可以绑定到这个槽函数上了。要求信号给槽的参数,可以富裕。但是不能少。

带有参数的信号,要求信号的参数和槽的参数要一致。类型、个数要满足要求(信号的参数个数要多于槽的参数个数,也可以相等)。

Qt中如果要让某个类能够使用信号槽,(可以在类中定义信号和槽函数)。则必须要在类的最开始的地方,写下Q_OGJECT宏。会进行宏展开,最终展开的效果会得到一系列很复杂的代码。这些代码就涉及到了Qt实现的内部原理了,这里就不深入研究了,如果不加这个宏,这个类就会编译报错。

三、信号和槽的意义

所谓的信号槽,终究要解决的问题,就是响应用户的操作。信号槽,其实在GUI开发各种框架中,是一个比较有特色的存在。这是一个高情商的说法,其他的GUI开发框架,搞的方式都要更简洁一些。网页开发(js + dom api)网页开发中响应用户的操作,主要就是挂回调函数。不要搞一个单独的connect完成上述的信号槽连接。处理函数u,就像控件的一个属性/成员一样。大部分的GUI开发框架都是这么搞的。

Qt信号槽,connect这个机制,设想很美好的。

1)解耦合,把触发用户操作的控件和处理对应用户的操作逻辑解耦合。

2)多对多的效果:一个信号可以connect到多个槽函数上。一个槽函数,也可以被多个信号connect。前端开发只能一对一。一个事件只能对应一个处理函数。Qt谈到的一个多对多和我们的数据库使用的多对多是相似的。数据库中是通过关联表实现的。Qt也是这样的。

综上所述,Qt引入信号和槽机制,最本质的目的。就是为了能够让信号和槽之间按照多对多的方式来进行关联。其他的GUI框架往往是不具备这样的特性的。实际上,随着程序开发这个事情,大家的经验越来越多。其实在GUI开发的过程中,多对多这个事情,其实是个伪需求,实际开发很少会用到绝大部分情况,一对一就够用了。新出现的一些图形化开发框架,很少有在继续支持这种多对多的了。Qt有很多的优点,值得我们区深究。

四、信号和槽断开连接

1、使用disconnect来断开信号槽的连接。这个东西用的比较少,大部分的情况下,把信号和槽连接了以后,就不必管了。

 代码:

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->pushButton_2,&QPushButton::clicked,this,&Widget::Handler1);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::Handler1()
{
    setWindowTitle("按钮1");
}

void Widget::Handler2()
{
    setWindowTitle("改变成功");
}



void Widget::on_pushButton_clicked()
{
    //解除按钮1原有的信号和槽函数的连接
    disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::Handler1);
    //重新连接信号和槽函数的连接
    connect(ui->pushButton_2,&QPushButton::clicked,this,&Widget::Handler2);
}

 切换原来信号绑定的槽函数。

2、定义槽函数的时候,也是可以使用lambda表达式的!很多编程语言都支持,语法糖。本质就是一个匿名函数,主要应用在回调函数场景中。lambda表达式是一个回调函数。这个函数无法获取到外面的变量的。lambda为了解决上述问题,引入了变量捕获。写作[=]把上层的变量都给捕获。后续对应的槽函数比较简单,而且是一次性使用的,就经常会写作这种lambda的形式。另外也要确认捕获的lambda内部的变量是有意义的。无论何时用户点击了按钮,捕获到的变量都能正确使用。

代码:

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("按钮lambda");
    connect(button,&QPushButton::clicked,this,[=](){
        button->move(100,200);
    });
}

Widget::~Widget()
{
    delete ui;
}

小结:

1、信号槽是啥 信号源,信号的类型,信号的处理方式。

2、信号槽的使用 connect。

3、如何查阅文档,一个控件,内置了哪些信号,信号都是何时触发。一个控件内置了哪些槽,槽都是什么作用。

4、自定义槽函数。还可以让Qt Creator自动生成。

5、自定义信号 信号本质就是一个成员函数,函数的定义是Qt自己生成的,咱们只需要写函数声明。

6、信号和槽还可以带有参数,类型匹配,信号的参数要多于槽的参数。

7、信号槽存在的意义 解耦合 多对多的效果。

8、disconnect使用方式。

9、lambda表达式,简化槽函数的定义。

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

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

相关文章

Bugku---密码学---乐谱密码

题目出处&#xff1a;首页 - Bugku CTF ✨打开后发现是一张乐符图 ✨一般我们所熟悉的「Do Re Mi Fa Sol La Si」&#xff0c;若写成音名&#xff0c;即是「C D E F G A B」。不过德国人习惯使用的音名则是「C D E F G A H」&#xff0c;「B」代表 音名B♭ 。 C也就是后面的4&…

Rust练手项目,写个有趣的小工具定时从一言网获取一段有趣的话并推送通知

Rust练手项目&#xff0c;写个有趣的小工具 代码 继续练习Rust, 写个小工具定时从一言网获取一段有趣的话并提示&#xff0c;如下 练习以下Rust点 并发编程 Mutex, Arc指针使用HTTP请求Windows Gui 代码 Cargo.toml [package] name "funny_word" edition "20…

YOLOv8目标检测模型——遥感小目标检测经验分享

小目标检测——YOLOV8 一、引言 背景介绍 &#xff08;1&#xff09;目标检测的重要性 目标检测在许多领域都具有极其重要的作用。在自动驾驶中&#xff0c;目标检测能够识别道路上的障碍物和行人&#xff0c;确保行车安全。在视频监控中&#xff0c;目标检测能够实时发现异…

【matlab】生成 GIF 的函数(已封装可直接调用)

文章目录 前言一、函数输入与输出二、函数代码三、例程&#xff08;可直接运行&#xff09;参考文献 前言 生成 gif 图片时遇到的问题&#xff0c;为了后续调用方便&#xff0c;封装为函数 一、函数输入与输出 输入&#xff1a; cell_figure: cell 数组&#xff0c;数组元素是…

Chainlit集成LlamaIndex并使用通义千问模型实现AI知识库检索网页对话应用增强版

前言 之前使用Chainlit集成LlamaIndex并使用通义千问大语言模型的API接口&#xff0c;实现一个基于文档文档的网页对话应用。 可以点击我的上一篇文章《Chainlit集成LlamaIndex并使用通义千问模型实现AI知识库检索网页对话应用》 查看。 本次针对上一次的代码功能进一步的完善…

Cursor与Copilot:编程界的双雄对决

引子 在技术快速发展的当下&#xff0c;编程几乎成为了现代社会的基础能力。Cursor与Copilot作为当前备受瞩目的编程助手&#xff0c;各自展现出了独特的魅力。它们不仅改变了程序员的工作方式&#xff0c;更是提升了代码编写的效率&#xff0c;成为了编程界的“双雄”。 Curs…

软件安全、逆向分析、加密与解密--crackme2详解

本次使用到的软件有&#xff1a;PEiD、IDA、X32dbg 刚学逆向不久&#xff0c;可能有些地方会有错误&#xff0c;欢迎各位大佬指导 执行 运行程序 点击About 点击确定&#xff0c;输入如图数据 点击try Now 点击确定&#xff0c;回到主界面 点击Exit&#xff0c;退出 查壳&a…

Docker:SpringBoot项目创建Docker镜像并推送到阿里云容器镜像仓库

0. 准备工作 os&#xff1a;macos 15.0 jdk&#xff1a;1.8 docker&#xff1a;26.0.0 1. 阿里云容器镜像服务创建实例 创建个人版 个人实例创建成功 个人镜像加速器地址 2. 安装Docker Desktop Docker Desktop是Docker的一个集成工具&#xff0c;非必须&#xff0c;过程…

指纹与指甲检测系统源码分享

指纹与指甲检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

这个时代唯一“不变“的又是{变}

这个时代唯一不变的就是“变”&#xff0c;所以每个人都得有规划意识&#xff0c;首先要对自己的价值有清晰的认知&#xff0c;你核心卖点是什么。第二&#xff0c;你取得的成绩是通过平台成就的还是通过自身努力取得的&#xff0c;很多人在一家平台待久了之后&#xff0c;身上…

在Unity UI中实现UILineRenderer组件绘制线条

背景介绍 在Unity的UI系统中&#xff0c;绘制线条并不像在3D世界中那样直观(使用Unity自带的LineRender组件在UI中连线并不方便,它在三维中更合适)。没有内置的工具来处理这种需求。如果你希望在UI元素之间绘制连接线&#xff08;例如在UI上连接不同的图标或控件&#xff09;&a…

26. 构建一个矩形平面几何体

定义矩形几何体顶点坐标 一个矩形平面&#xff0c;可以至少通过两个三角形拼接而成。而且两个三角形有两个顶点的坐标是重合的。 注意三角形的正反面问题&#xff1a;保证矩形平面两个三角形的正面是一样的&#xff0c;也就是从一个方向观察&#xff0c;两个三角形都是逆时针…

Docker 消息队列RabbitMQ 安装延迟消息插件

介绍 RabbitMQ的官方推出了一个插件&#xff0c;原生支持延迟消息功能。该插件的原理是设计了一种支持延迟消息功能的交换机。当消息投递到交换机后可以暂存一定时间&#xff0c;到期后再投递到队列。 查看版本号 docker exec rabbit名字 rabbitmqctl version根据版本下载 插…

neo4j(spring) 使用示例

文章目录 前言一、neo4j是什么二、开始编码1. yml 配置2. crud 测试3. node relation 与java中对象的关系4. 编码测试 总结 前言 图数据库先驱者 neo4j&#xff1a;neo4j官网地址 可以选择桌面版安装等多种方式,我这里采用的是docker安装 直接执行docker安装命令: docker run…

一键更换软件源的工具——chsrc

前言 经常用pip&#xff0c;ubuntu的apt&#xff0c;或者centos的yum等包下载工具的人不可避免的一件事就是——“更换软件源”&#xff0c;因为以上三个包下载工具的软件源一般都是默认为国外的官方网站&#xff0c;由于国情问题&#xff0c;下载速度就会非常慢&#xff0c;所…

华为OD机试 - 最大矩阵和 - 卡德恩算法(动态规划)(Python/JS/C/C++ 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

线性代数(宋浩版)(4)

2.4逆矩阵 &#xff08;不要把矩阵放在分母上&#xff09; 方阵的行列式 性质1 性质2 性质3 伴随矩阵&#xff08;只有方阵才有&#xff09; 1.求出所有元素的代数余子式&#xff08;矩阵先求行列式&#xff09;。 2.按行求的代数余子式按列放。 定理1&#xff08;重要&…

MySQL篇(窗口函数/公用表达式(CTE))

目录 讲解一&#xff1a;窗口函数 一、简介 二、常见操作 1. sumgroup by常规的聚合函数操作 2. sum窗口函数的聚合操作 三、基本语法 1. Function(arg1,..., argn) 1.1. 聚合函数 sum函数&#xff1a;求和 min函数 &#xff1a;最小值 1.2. 排序函数 1.3. 跨行函数…

ECMAScript与JavaScript的区别

目录 一、什么是ECMAScript&#xff1f; 二、什么是JavaScript&#xff1f; 三、ECMAScript与JavaScript的关系 3.1 ECMAScript规范版本 3.2 JavaScript的实现 四、ECMAScript与JavaScript的主要区别 4.1 规范与实现的区别 4.2 版本更新 4.3 环境支持 4.4 语言特性 五…

C# 使用Socket通信,新建WinForm服务端、客户端程序

一、新建WinForm Socket服务端程序 注&#xff1a;rtbReceviceMsg为RichTextBox控件 服务端程序、界面 服务端代码 public partial class Form1 : Form {public Form1(){InitializeComponent();}public virtual void TriggerOnUpdateUI(string message){if (this.InvokeRequir…