QT之信号和槽

在刚刚了解Qt的时候,为了通过按钮显示 hello world 的时候曾说明过信号与槽,虽然没有细说,不过也算是接触过。

而本文就会细细说明什么是 Qt 的信号与槽。

概念初识

在 linux 学进程相关的内容的时候,曾了解过信号是操作系统控制进程的一种方式,可以看作是操作系统和进程通信的方式,而 Qt 的信号与槽实际上也差不多。

Qt中的信号三要素

  • 信号源:发出信号的控件
  • 信号类型:用户进行不同的操作会发送不同的信号,比如按钮被点击了,某个文本被复制了,鼠标光标被移动的信号,这些信号都需要区分
  • 槽:槽实际上就是一个信号到来时所需要执行的函数

在 Qt 中需要先通过 connect 函数将信号和槽关联起来,当特定的信号到来时,就触发特定的槽,执行特定的函数。

而 Qt 中一个类如果使用信号与槽,必须在类内部带一个宏。

这个宏会展开成很长一段的代码,想要使用信号与槽必须带它。 

connect函数

connect(      const QObject* sender,

              const char * signal,

              const QObject* receiver,

              const char * method,

              Qt::ConnectionType type = Qt::AutoConnection() );
  • sender : 信号源,即发出信号的控件
  • signal :信号,即信号源发出的信号类型
  • receiver : 接收者,即处理信号的控件
  • method : 槽,即接收者处理信号的动作/函数
  • type : 用于指定的关联方式,不过一般采用缺省的 AutoConnection 方式
小实验

我们做一个实验,即以代码的方式实现通过点击按钮关闭窗口。

在 MainWindow.cpp 中初始化一个按钮,并且通过 connect 函数将 QPushButton 中的 clicked 信号和 QMainWindow 中的 close 槽函数关联起来。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("closed");
    button->move(300,300);
    connect(button,&QPushButton::clicked,this,&QMainWindow::close);
}

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

其中,在控件中都有类似的情况,比如 QPushButton 中出现了 click 和 clicked,这二者有什么不同呢?

实际上第一个 click 是一个槽函数,它的作用就是模拟一次点击的动作。

而第二个 clicked 则是一个信号,它是该按钮被点击之后所发送的信号。 

而区别二者就是通过前面的图标

 这个图标表示一个槽函数。

这个图标表示一个信号。

使用 connect 函数可以通过这个图标区别是信号还是槽函数。

QT5之后的connect函数

上述的connect函数第二个参数和第四个参数都是一个 const char* 类型的,但是我们使用时传入的却是函数,这实际上会引发类型错误。

实际上上述的 connect 函数是 QT5 之前的函数,当时传入信号和槽还需要通过 SIGNAL 和 SLOT 两个宏函数来进行转化。

现在更新后就可以直接传了。

 自定义槽函数

作为一个前端工具,只有类自带的槽函数是不够用的,因此我们需要自定义槽函数。

而自定义槽函数的连接有两种方式:通过 connect 函数进行连接和通过 图形化界面生成一个槽函数,QT自己通过 connectSlotByName 进行连接。

通过代码形式

可以自己初始化一个控件,然后定义一个函数后通过connect 函数绑定信号与槽。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("hell world");
    connect(button,&QPushButton::clicked,this,&MainWindow::Handlerclicked);
}

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

void MainWindow::Handlerclicked()
{
    this->setWindowTitle("clicked!");
}

如果这个空间是在 ui 界面直接通过拖拽方式得到的话,就需要从 ui 这个对象中获取。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::Handlerclicked);
}

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

void MainWindow::Handlerclicked()
{
    ui->pushButton->setText("hellworld");
}

这样就能够通过代码形式 connect 自定义的槽函数。

通过图形化界面形式

当我们拖拽一个控件到画布上时,通过右键点击可以发现有一个 “转到槽” 的选项。

点击 “转到槽” 之后即可选择这个槽函数所连接的信号。

选择信号后,发现 QT 自动生成了一个函数。

其名字由发出信号的控件名以及发出的信号组成。

通过这种方式也可以将控件的信号与槽函数连接起来,而不用通过connect 函数连接。

并且这个生成的函数名不可修改,因为QT就是通过这个函数名和信号进行连接的,我们可以实验一下。

 其中 connectSlotsByName 是一个函数,表示通过函数名来连接槽函数与信号,因此函数名不可随便修改。

自定义信号

在QT中,我们也可以自定义信号,虽然在开发场景中很少用,不过还是需要了解一下。

QT中的信号实际上就是一个函数,我们看一看如何实现。

在 MainWindow.h 中声明一个函数,其中这个函数前面需要用 signals 关键字修饰一下。

然后在 MainWindow.cpp 中绑定即可。 

绑定的 HandlerSignal 函数会将窗口的标题修改成 "自定义信号"。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(this,&MainWindow::mySignal,this,&MainWindow::HandlerSignal);
}

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


void MainWindow::HandlerSignal()
{
    this->setWindowTitle("自定义信号");
}

 但是这里只是将信号和槽绑定了而已,但是这个信号并没有发出。

因此我们可以通过间接的方式来发送信号。

这里将 pushButton 的 clicked 信号和 HandlerPush 槽函数绑定,而这个槽函数内部会发送一个 mySigal 信号。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(this,&MainWindow::mySignal,this,&MainWindow::HandlerSignal);
    QPushButton* pushbutton = new QPushButton(this);
    pushbutton->setText("发送 mySignal 信号");
    connect(pushbutton,&QPushButton::clicked,this,&MainWindow::HandlerPush);
    pushbutton->move(200,200);
}

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


void MainWindow::HandlerSignal()
{
    this->setWindowTitle("自定义信号");
}

void MainWindow::HandlerPush()
{
    emit mySignal();
}

虽然QT5之后可以不用带 emit 关键字就能发送信号了,但是一般还是带上,防止出现错误。 

                                        emit : 发送信号的关键字。

点击之后,发现 window 的标题改变了,证明确实发送了 mySignal 信号了。

带参数的信号与槽

Qt 的信号与槽函数都能够带参数,就像函数传参一样。

无论自定义的信号与槽还是 Qt 自带的信号与槽都有参数,不过它们都需要遵守一定的规则。

  • 信号的参数和槽的参数类型必须相同,即信号的参数1和槽的参数1类型必须相同
  • 信号的参数个数可以和槽的参数个数可以不同,即信号的参数个数可以比槽的参数个数多

当一个信号被发送时,如果槽函数在遵守上面的规则的情况下需要信号传参,那么 Qt 会将信号的参数作为实参发送给槽函数,我们可以试一试。

首先通过图形化界面设置两个按钮,并且通过图形化界面形式自定义槽函数。

然后再自己设置自己的信号和槽函数,它们都带有参数,并且形式符合规则。 

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QString>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void MyHandlerSignal(const QString& text);
signals:
    void mysignal(const QString& text);

private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

两个按钮都会发送一个带有参数的信号,而这个信号对应的函数会修改窗口的名称。

接着我们运行一下看看会发生什么?

 可以看到窗口的标题确实修改了。并且修改成了信号所带的参数。

 除了自定义的槽函数和信号有参数之外,一些自带的槽函数和信号也有参数。

比如这个 PushButton 的控件,其 clikced 信号既有没参数的,也有带参数的。

 取消信号槽的连接

信号和槽不仅可以通过 connect 连接,也能够通过 disconnect 函数取消连接。

因为 Qt 的信号槽机制支持多对多连接,即一个信号可以绑定多个槽函数,一个槽函数也能够绑定多个信号,虽然这个机制不常用,但是如果忽略可能会出现错误。

因此如果有需求的话,可以通过 disconnect 先取消连接,再重新连接其他的信号与槽。

我们可以实验一下,这里有两个按钮,第一个按钮和 Handler1 建立连接,Handler1 会修改窗口名称为 Handler1.

第二个按钮会将第一个按钮和 Handler1 的连接断开,然后和 Handler3 建立连接。

Handler3 会修改窗口的名称为 Handler3.

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::Handler1);
     connect(ui->pushButton_2,&QPushButton::clicked,this,&MainWindow::Handler2);
}

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

void MainWindow::Handler1()
{
    this->setWindowTitle("Handler1");
}

void MainWindow::Handler2()
{
    //取消第一个按钮的连接
    disconnect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::Handler1);
    //连接其他的槽
    connect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::Handler3);
}

void MainWindow::Handler3()
{
    this->setWindowTitle("Handler3");
}

 可以看到结果正如预期所料。

如果这里不提前 disconnect 的话,那么按钮1 就会同时和 Handler1 和 Handler3 建立连接,导致一对多的情况出现。

采用 lambada 表达式作为槽函数 

在 Qt 5 以及更高版本的 Qt 下, 一般默认采用的 C++11 编译,因此可以使用 lambada 表达式。

而如果是 Qt4 以及更低的版本,就需要添加指令来采用 C++11 编译。

 这里我们就是用 lambada 表达式作为槽函数。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("按钮");
    connect(button,&QPushButton::clicked,this,[=](){
       button->move(300,300);
    });
}

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

发现确实执行了 lambada 表达式函数的内容。 

 总结

本文讲解了什么是信号与槽,也讲了信号槽的使用方法,比如自定义信号和槽函数,带参数的信号与槽,disconnect 的使用和 lambada 表达式的槽函数。

总的来说信号槽这个机制挺优秀,但是其他的 GUI 工具并没有使用信号槽的机制,比如 java 的GUI开发就是通过类似赋值的手段来将某一个函数与信号关联起来,而不是用 connect 函数来连接。

connect 函数连接可能比较繁琐,但是它实现了代码的低耦合,虽然也足够优秀,但是对比市场上的可能有点不足,这是由于时代的局限性造成的。

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

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

相关文章

【STM32】快速使用F407通用定时器输出可变PWM

网上的文章太啰嗦&#xff0c;这里直接开始。 使用的是STM32CubeIDE&#xff0c;HAL。以通用定时器TIM12在 通道2上输出1KHz的PWM为例。 要确定输出的引脚、定时器连接在哪里。 TIM2、3、4、5、12、13、14在APB1上&#xff0c;最大计数频率84M。 TIM1、8、9、10、11在APB2…

Python 与 TensorFlow2 生成式 AI(二)

原文&#xff1a;zh.annas-archive.org/md5/d06d282ea0d9c23c57f0ce31225acf76 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第四章&#xff1a;教授网络生成数字 在前一章中&#xff0c;我们涵盖了神经网络模型的构建基块。在这一章中&#xff0c;我们的第一个项目…

HIVE启动步骤

不如意的时候不要尽往悲伤里钻 想想有笑声的日子 启动HIEV 1.启动虚拟机Hadoop集群 2.连接Linux 3.start-all.sh 4.hive 5.hive启动时报错 当我们启动Hadoop集群时 启动hive可能会出现卡在true处不动的情况 那么我们只需要做一个操作就可以解决问题啦 hdfs haadmin -transitio…

ASP.NET数据存储与交换系统设计

摘 要 该系统以Microsoft Visual Studio 2003作为开发工具&#xff0c;选用SQL Server 2000数据库来实现数据存储&#xff0c;并设计开发了一种基于B/S模式的数据存储与交换系统。该系统完成了用户注册管理、后台管理和用户空间管理功能&#xff1b;为每个用户提供了个人的存…

数据库(MySQL)—— DQL语句(基本查询和条件查询)

数据库&#xff08;MySQL&#xff09;—— DQL语句&#xff08;基本查询和条件查询&#xff09; 什么是DQL语句基本查询查询多个字段字段设置别名去除重复记录 条件查询语法条件 我们今天进入MySQL的DQL语句的学习&#xff1a; 什么是DQL语句 MySQL中的DQL&#xff08;Data Q…

【ARM 裸机】NXP 官方 SDK 使用

在前几节中&#xff0c;学习了如何编写汇编的 led 驱动、C 语言的 led 驱动、模仿 STM32 进行开发&#xff0c;我们都是自己写外设寄存器的结构体&#xff0c;外设非常的多&#xff0c;写起来费时费力&#xff0c;NXP 针对 I.MX6ULL 编写了一个 SDK 包&#xff0c;这个 SDK 包就…

python的输入输出(爽文,备忘,查询,友好)

Python中的输入输出主要涉及到输入函数和输出函数。 输出函数&#xff1a;print() print() 函数用于将信息输出到屏幕上。它可以输出字符串、变量的值&#xff0c;以及其他各种数据类型。 name "Alice" age 30 print("姓名:", name, "年龄:&quo…

OpenCV如何实现背投(58)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV直方图比较(57) 下一篇&#xff1a;OpenCV如何模板匹配(59) 目标 在本教程中&#xff0c;您将学习&#xff1a; 什么是背投以及它为什么有用如何使用 OpenCV 函数 cv::calcBackP…

轻松下载小程序短剧视频,你不可错过的神器!

探索小程序的精彩短剧&#xff0c;一款神器让您的收藏变得触手可及。轻松下载&#xff0c;无损享受&#xff0c;每个精彩瞬间&#xff0c;都不让你错过。让这款下载神器成为你掌中的宝藏&#xff0c;开启随时随地的精彩观影体验。 这个神器就是下载高手 下载小程序视频的工具…

GoLang Gin实际使用

所有代码同步到Admin/gitDemo - Gitee.comhttps://gitee.com/mec-deployment-team_0/git-demo/tree/dev/ 1.创建Gin框架 一般设计一个常规的web项目&#xff0c;都需要以下几个模块 runApp 主函数&#xff0c;运行整个项目routes 路由控制&#xff0c;管理跳转以及路由分组co…

CTF-Show nodejs

web334 下载附件&#xff0c;有两个文件 在Character.toUpperCase()函数中&#xff0c;字符ı会转变为I&#xff0c;字符ſ会变为S。 在Character.toLowerCase()函数中&#xff0c;字符İ会转变为i&#xff0c;字符K会转变为k。 所以用ctfſhow 123456登录就可以出flag了 w…

力扣刷题第一天:消失的数字

大家好啊&#xff0c;从今天开始将会和大家一起刷题&#xff0c;从今天开始小生也会开辟新的专栏。&#x1f61c;&#x1f61c;&#x1f61c; 目录 第一部分&#xff1a;题目描述 第二部分&#xff1a;题目分析 第三部分&#xff1a;解决方法 3.1 思路一&#xff1a;先排序…

LeetCode1005:K次取反后最大化的数组和

题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&#xff0c;返回数组 可能的最大和 。 解题思想 第…

AC自动机

AC自动机 AC自动机有一个很出色的功能&#xff1a;实现多模式匹配。 多模式匹配&#xff1a;模式串有多个&#xff0c;主串只有一个&#xff0c;要进行多次模式串匹配。如果用KMP就要一个一个模式串进行匹配&#xff0c;效率低。AC自动机就可以做到&#xff0c;只要经过一些预…

深度学习中权重初始化的重要性

深度学习模型中的权重初始化经常被人忽略&#xff0c;而事实上这是非常重要的一个步骤&#xff0c;模型的初始化权重的好坏关系到模型的训练成功与否&#xff0c;以及训练速度是否快速&#xff0c;效果是否更好等等&#xff0c;这次我们专门来看看深度学习中的权重初始化问题。…

RuoYi-Vue-Plus (SPEL 表达式)

RuoYi-Vue-Plus 中SPEL使用 DataScopeType 枚举类中&#xff1a; /*** 部门数据权限*/DEPT("3", " #{#deptName} #{#user.deptId} ", " 1 0 "), PlusDataPermissionHandler 拦截器中定义了解析器&#xff1a; buildDataFilter 方法中根据注解的…

基于电磁激励原理利用视触觉传感器估计抓取力矩的方法

由于触觉感知能使机器人通过其触觉传递获取丰富的接触信息&#xff0c;触觉感知已经成为机器人机械臂的一种流行的感知方式。而在触觉传感器可获取的各种信息中&#xff0c;通过外界接触从抓取物体传递到机器人手指的力矩等信息&#xff0c;在完成各种指令的实现尤为重要。如图…

数据结构––队列

1.队列的定义 2.队列的分类 2.1循环队 2.2链式队 3.队列的实现 3.1循环队 3.1.1声明 typedef int QDataType; #define MAXSIZE 50 //定义元素的最大个数 /*循环队列的顺序存储结构*/ typedef struct {QDataType *data;int front; //头指针int rear; //尾指针 }Queue;…

【最大公约数 排序】2344. 使数组可以被整除的最少删除次数

本文涉及知识点 最大公约数 排序 LeetCode2344. 使数组可以被整除的最少删除次数 给你两个正整数数组 nums 和 numsDivide 。你可以从 nums 中删除任意数目的元素。 请你返回使 nums 中 最小 元素可以整除 numsDivide 中所有元素的 最少 删除次数。如果无法得到这样的元素&a…

Apache中如何配置 ws 接口

Apache中如何配置 wss 接口 在Apache中配置WebSockets的支持&#xff0c;你需要使用mod_proxy_wstunnel模块&#xff0c;该模块是Apache的一个代理模块&#xff0c;它允许你代理WebSocket请求。 以下是配置步骤的简要说明和示例&#xff1a; 确保你的Apache服务器安装了mod_…