信号与槽
都说信号与槽是QT的精髓(别问谁说的,问就是我说的),那么我们首先先知道什么是信号和槽。
信号就是信号,可以由任何组件去发送,而QT提供的组件可可以发送信号,比如QPushButton(没错,还是以它为例),当我们按下QPushButton的时候,实际上QPushButton会发出信号,那为什么我们看不到任何变化呢,那是因为我们没有给这个信号绑定槽函数。
绑定之后,每当我们绑定的信号发出后,QT会自动执行我们绑定的槽函数。
简单来说就是A给B发个信号,然后B去执行槽函数。
接下来我们直接写一段代码来实验一下,马上就明白是怎么回事了。
初体验
如上图,我给一个QPushButton绑定了一个来自于主界面的槽函数,一旦点击之后便会打印调试信息“button1 click”。
下面是主界面的头文件以及cpp文件
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_Zhetu.h"
class Zhetu : public QMainWindow
{
Q_OBJECT
public:
Zhetu(QWidget *parent = nullptr);
~Zhetu();
public slots:
void ButtonClick(void);
private:
Ui::ZhetuClass ui;
};
上面是头文件,我们只需要注意一点,那就是public slots:
我们一个组件类的公共槽函数就写在这里面,和public:以及private:没有什么不一样的。
#include "Zhetu.h"
#include <QPushButton>
#include <qdebug.h>
void Zhetu::ButtonClick(void) {
qDebug() << "button1 click";
}
Zhetu::Zhetu(QWidget *parent): QMainWindow(parent){
this->setFixedSize(500, 500);
QPushButton* button1 = new QPushButton("button1", this);
connect(button1, &QPushButton::clicked, this, &Zhetu::ButtonClick);
}
Zhetu::~Zhetu()
{}
上面是cpp文件,我在主界面的构造函数里创建了一个QPushButton,并且通过connect函数给这个按钮绑定了一个信号槽。
上面代码的意思就是绑定了button1的点击(clicked),只要button1被点击了,那么就会发出一个信号,信号谁会去接收呢,就是connect的第三个参数去接收,this也就是我们的主界面,主界面接收到来自button1的点击信号之后会去调用主界面下的槽函数ButtonClick。
在ButtonClick函数中我们只有一句代码,也就是打印调试信息(不知道这个写法没关系,记住就好了,QT中我们很少打印的)。
connect
经过上面的例子,相信小伙伴们应该对信号槽有了一些了解了。接下来我们就介绍一下connect的几种用法(因为它有好多重载版本)。
第一种
第一种用法就是我上面演示的例子
connect(发送者,信号,接受者,槽函数)
第一个参数填入信号的发送者,比如说我们的按钮,那就直接把按钮的这个对象传进去。
第二个参数的发送者发送的信号,这个信号我们可以通过QT助手去查找。我们的QPushButton里没有信号,那么我们就去它的父类里寻找,也就是QAbstractButton。
可以看出一共有四种信号,相信大家通过英文名字都可以知道这些信号是什么意思,就比如说我们刚刚例子里用的clicked,那就是点击按钮的时候发送的信号。
第三个参数传入接收者,我在例子中写的是this,也就是说我Zhetu类来接收。
第四个参数传入槽函数的地址,我们自己定义的类的自定义槽函数的声明我们写在头文件的publish slots:中,可以参考上面的代码例子。
这样我们就算完成了信号与槽的绑定,只要信号发送了,那么我们的接收者就会调用绑定好的槽函数。
如果像我上面那样绑定槽函数,在槽函数里只是打印一句话,那么是不是没有接收者的事,不管谁是接收者,我都可以打印一句话。所以实际上我们在第四个参数的位置可以随便传入一个参数,不一定非要是接收者的槽函数。
那么既然都跟接收者没有关系了,我是不是连接收者都可以省略。
第二种
这就是第二种用法,如果我的槽函数是随便一个函数(不是其他类的槽函数),那么connect只需要三个参数。
connect(发送者,信号,执行函数)
前两个参数与上面一致,第三个参数传入执行函数的地址。
但是要注意的是,如果执行函数是其他类的槽函数,那么就不能用这第二种用法,必须要指定接收者。
也就是说,要么就不用其他类的槽函数,一旦用了,那么接受者就要写上这个类的对象。
第三种
上面的用法是QT5之后的,QT4的话,connect有别的用法。
connect(button1, SIGNAL(clicked(bool)), this, SLOT(ButtonClick(void)));
简单来说就是信号和槽函数分别需要用不同的宏给包裹起来,分别是SOGNAL以及SLOT
还有一点要注意的就是写上信号函数与槽函数的时候需要加上参数类型,参考一下上面的例子。
所以说QT4的写法还是比较麻烦的,不过这是向下兼容的,QT5可以用QT5和QT4的写法,而QT4只能用QT4的写法。不过话又说回来了,现在QT都出到6了,QT4除了老项目以外应该也用的比较少了,所以这第三种用法(QT4)大家了解一下就行。
槽函数的参数
上面QT4的写法中我们知道写信号函数以及槽函数的时候需要写上参数类型,那么就引申出新的问题了,槽函数还可以有参数?该怎么给槽函数传参数呢?
这个就得看我们的信号了,实际上信号也是函数,信号也有参数,当然我们使用QT人家组件的信号的时候我们是无法决定给信号的参数是什么的,这个在后面将自定义信号的时候会说。
总之就是信号函数的参数就是传给槽函数的参数。
还是以我们QPushButton的clicked这个信号为例,它有一个参数,是布尔类型的,所以实际上我们给这个信号绑定的槽函数是可以有一个可读取到的bool的参数的。
但是我们之前的例子很明显,绑定的槽函数都是没有任何参数的,所以信号传递的参数我们没有接收,接下来我们再写一段代码去验证一下能不能接收到来自信号的参数。
#include <qdebug.h>
void hello(bool a) {
qDebug() << a;
}
Zhetu::Zhetu(QWidget *parent): QMainWindow(parent){
this->setFixedSize(500, 500);
QPushButton* button1 = new QPushButton("button1", this);
connect(button1, &QPushButton::clicked, &hello);
}
我们是可以接受到来自信号的数据的,但是我们发现传递的这个永远是false。
此时这个参数实际上是不生效的,也就是一直都是传给我们默认值也就是false。我们加一行代码让这个参数生效即可。
button1->setCheckable(true);
当这个参数生效之后我们可以发现,接受的数据是true和false交替着来的,也就是说我们可以通过修改槽函数的形参列表来获取信号发送来的数据。
我们槽函数的形参数量可以比信号的形参数量少,但是不能比信号的形参多,否则会出现错误。
自定义信号与槽
我们知道了信号和槽都是函数,那么既然是函数,能不能重载呢?
答案是可以的。
但是我目前没看到有QT自带的组件的信号有重载的,那么相应的,信号的参数都是只有一种,没有重载,那么接收信号的数据的槽函数自然也是没有重载的了。
因此要实现重载功能,我们就需要自己去自定义信号和槽,没错,信号也是我们可以自己创建的。
自定义信号
自定义信号实际上就是我们去声明一个函数,这个函数要求是无返回值的。
我们写在头文件中的signals:里,可以参考下面的代码。
这样就算是完成了自定义的信号了,没错,信号只需要声明而不需要实现,可以有参数可以重载。
现在的问题就在于我们自定义的信号什么情况下会发送,人家QT自带的组件的信号都是我们有所操作的时候会自动发送,而我们自己定义的信号呢?那就只能是手动发送信号了。
使用emit就可以发送我们的自定义信号了,甚至还可以直接把参数写上。
emit this->MySignal();
重载之后就会有一个问题,我们使用connect的时候传入信号与槽的时候都是直接写函数名的,那人家咋知道你这个信号到底是你重载的哪个版本的信号,槽函数又是重载的哪个版本的槽函数呢?
答案就是函数指针,我们在connect绑定之前先创建好重载函数的函数指针,在创建函数指针的时候我们就把参数类型指定好,这样就不会有因为重载而出现歧义的情况出现了。
问题都解决之后我们就可以写代码来实验一下了。
#include "Zhetu.h"
#include <QPushButton>
#include <qdebug.h>
void Zhetu::MySlot(void) {
qDebug() << "button1 void";
}
void Zhetu::MySlot(int) {
qDebug() << "button1 int";
}
void Zhetu::test(void) {
emit this->MySignal();
emit this->MySignal(1);
}
Zhetu::Zhetu(QWidget *parent): QMainWindow(parent){
this->setFixedSize(500, 500);
QPushButton* button1 = new QPushButton("button1", this);
connect(button1, &QPushButton::clicked, this, &Zhetu::test);
void (Zhetu:: * fun1)(void) = &Zhetu::MySlot;
void (Zhetu:: * sig1)(void) = &Zhetu::MySignal;
connect(this, sig1, this, fun1);
void (Zhetu:: * fun2)(int) = &Zhetu::MySlot;
void (Zhetu:: * sig2)(int) = &Zhetu::MySignal;
connect(this, sig2, this, fun2);
}
由于我们无法自动的发出信号,我们只能手动去发送,因此我还是先给按钮绑了一个信号槽,当按钮按下时触发名为test的槽函数。在test里我们手动发送信号,并且我们在发送的时候发送了两条信号,因为一个有参数一个没有参数,因此在发送的时候我们不需要考虑重载的问题。
在绑定重载信号和重载槽函数的时候,我们就需要在调用connect之前就把对应的函数指针创建好,并且指定好参数,这样重载的问题就解决了。文章来源:https://www.toymoban.com/news/detail-833910.html
在上图我们也可以看出,我们是可以正常地发送重载信号也可以正常地使用重载槽函数去接收信号了。文章来源地址https://www.toymoban.com/news/detail-833910.html
到了这里,关于【快速上手QT】03-信号与槽connect的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!