QT之信号和槽

ops/2024/9/24 10:39:13/

在刚刚了解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_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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 函数连接可能比较繁琐,但是它实现了代码的低耦合,虽然也足够优秀,但是对比市场上的可能有点不足,这是由于时代的局限性造成的。


http://www.ppmy.cn/ops/36542.html

相关文章

HarmonyOS实战开发教程-如何开发一个2048游戏

今天为大家分享的是2048小游戏&#xff0c;先看效果图&#xff1a; 这个项目对于新手友友来说可能有一点难度&#xff0c;但是只要坚持看完一定会有收获。因为小编想分享的并不局限于ArkTs语言&#xff0c;而是编程思想。 这个游戏的基本逻辑是初始化一个4乘4的数组&#xff…

深入理解指针1

目录 如对您有帮助&#xff0c;还望三连支持&#xff0c;谢谢&#xff01;&#xff01;&#xff01; 1.内存和地址 计算机中常⻅的单位&#xff08;补充&#xff09;&#xff1a; 如何理解编址 2.指针变量和地址 2.1取地址操作符&#xff08;&&#xff09; 2.2指针变…

Spring Data JPA数据批量插入、批量更新真的用对了吗

Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作 前言 在前两篇文章已经…

小红书餐饮推广怎么合作?纯干货

小红书作为国内领先的生活方式分享平台&#xff0c;其用户群体主要集中在一二线城市&#xff0c;年龄分布在18-35岁之间&#xff0c;其中女性用户占比高达80%。这部分用户具有较高的消费能力、审美追求和品质生活需求&#xff0c;对美食有着极高的兴趣和消费意愿&#xff0c;为…

我独自升级崛起游戏账号登录注册教程 (5.8最新版)

新韩漫公司所发布的这项动作游戏已向玩家们敞开大门&#xff0c;为大家带来了前所未有的游戏体验和乐趣。这个游戏内包含了大量令人着迷的故事、令人印象深刻的战斗场景以及丰富多样的娱乐元素。在这其中最为引人注目的一点就是游戏内容中融入了“虚拟角色”的元素&#xff0c;…

【影片欣赏】【指环王】【魔戒:护戒使者 The Lord of the Rings: The Fellowship of the Ring】

2001年发行&#xff0c;Extended DVD Edition Part One 1. Prologue: One Ring to Rule Them All… 2. Concerning Hobbits 3. The Shire 4. Very Old Friends 5. A Long-expected Party 6. Farewell Dear Bilbo 7. Keep It Secret, Keep It Safe 8. The Account of Isildur 9…

java设计模式三

工厂模式是一种创建型设计模式&#xff0c;它提供了一个创建对象的接口&#xff0c;但允许子类决定实例化哪一个类。工厂模式有几种变体&#xff0c;包括简单工厂模式、工厂方法模式和抽象工厂模式。下面通过一个简化的案例和对Java标准库中使用工厂模式的源码分析来说明这一模…

【Kafka】1.Kafka核心概念、应用场景、常见问题及异常

Kafka 是一个分布式流处理平台&#xff0c;最初由 LinkedIn 开发&#xff0c;后成为 Apache 软件基金会的顶级项目。 它主要用于构建实时数据管道和流式应用程序。它能够高效地处理高吞吐量的数据&#xff0c;并支持消息发布和订阅模型。Kafka 的主要用途包括实时分析、事件源、…