Qt-信号与槽
1. 认识信号和槽
Qt中,谈到信号,涉及到三个要素.
- 信号源:由哪个控件发出的信号
- 信号的类型:用户进行不同的操作,就可能出发不同的信号
点击按钮,触发点击信号
在输入框中移动光标,触发移动光标的信号
勾选一个复选框
选择一个下拉框,都会触发出不同的信号- 信号的处理方式:槽(slot)=> 函数
Qt中可以使用 connect 这样的函数,把一个信号和一个槽关联起来,后续只要信号触发了,Qt就会自动的执行槽函数。
所谓的 “槽函数” 本质上也是一种 “回调函数”(callback)
回调函数:
- 最早 C 语言阶段
C 进阶 =>指针进阶 =>函数指针
1)实现转移表,降低代码的"圈复杂度"
2)实现回调函数效果 =>qsort- C++ 阶段
1)STL 中,函数对象/仿函数.
2)lambda 表达式- Linux 中
1)信号处理函数
2)线程的入口函数.
3)epol 基于回调的机制
2. Connect函数
在 Qt 中,QObject 类提供了⼀个静态成员函数 connect() ,该函数专⻔⽤来关联指定的信号函数和槽函数
connect函数的原型
// connect函数的原型
connect (const QObject *sender,// 描述了当前信号是哪个控件发出来的
const char * signal ,// 信号的类型
const QObject * receiver ,// 哪个对象负责处理
const char * method ,// 这个对象怎么处理
Qt::ConnectionType type = Qt::AutoConnection ) // 这个暂时不考虑
参数说明:
- sender:信号的发送者;
- signal:发送的信号(信号函数);
- receiver:信号的接收者;
- method:接收信号的槽函数;
- type:⽤于指定关联⽅式,默认的关联⽅式为 Qt::AutoConnection,通常不需要⼿动设定。
举一个实例:(此代码在Qt Creator上运行)
// Widget.cpp
#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("关闭");
button->move(400,300);
connect(button,&QPushButton::clicked,this,&Widget::close);
}
Widget::~Widget()
{
delete ui;
}
// Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
调用和声明的函数参数不匹配,这是由于他显示的是老版本的,新版本中会有重载函数匹配
3. 自定义槽函数
所谓的自定义一个 槽函数,操作过程和自定义一个普通的成员函数,没啥区别!!
在以前版本的 Qt 中,槽函数必须放到
public/private/protected slots:
此处的 slots 是 Qt 自己扩展的关键字,(不是 C++ 标准中的语法)
Qt 里广泛使用了 元编程 技术.(基于代码,生成代码)qmake 构建 Qt 项目的时候, 就会调用专门的扫描器, 扫描代码中特定的关键字.(slots 这种)基于关键字自动生成一大堆相关的代码.
举一个例子:按下按钮,更改窗口的标题。(Qt)
// Widget.cpp
#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("按钮");
// 自定义槽函数1
connect(button,&QPushButton::clicked,this,&Widget::handleClick);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()
{
// 按下按钮,更改窗口的标题
this->setWindowTitle("按钮已经按下!");
}
// Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:// 再之前版本中,自定义的槽函数要声明写在相应的下面
void handleClick();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
第二种方式自定义槽函数:
这个窗口就列出了 QPushButton 给我们提供的所有的信号(还包含了 QPushButton 父类的信号)
这种槽函数不用去用connect函数建立信号与槽函数的连接
按下按钮,修改窗口的标题
// WIdget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
// 连接信号槽(注意:名字不可以更改)
// 这个名字是 objectName 信号的名字
void Widget::on_pushButton_clicked()
{
this->setWindowTitle("按钮已经按下!");
}
// Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
运行结果:
如果我们通过图形化界面创建控件,还是推荐使用这种快速的方式来连接信号槽.
如果我们是通过代码的方式来创建控件,还是得手动 connect.(你的代码中没有调用 connectSlotsByName)
4. 自定义信号
所谓的 Qt 的信号,本质上也就是一个"函数"
Qt 5 以及更高版本中,槽函数和普通的成员函数之间,没啥差别了
但是,信号, 则是一类非常特殊的函数.
- 程序员只要写出函数声明,并且告诉 Qt,这是一个"信号"即可.这个函数的定义,是 Qt 在编译过程中,自动生成的,(自动生成的过程,程序员无法干预)
- 作为信号函数,这个函数的返回值,必须是 void有没有参数都可以. 甚至也可以支持重载,
这个也是 Qt 自己扩展出来的关键字~~
qmake 的时候, 调用一些代码的分析/生成工具
扫描到类中包含 signas 这个关键字的时候,此时,就会自动的把下面的函数声明认为是信号,并且给这些信号函数自动的生成函数定义
无参数的信号和槽
5. 带参数的信号和槽
信号和槽 也可以带参数
当信号带有参数的时候,槽的参数必须和信号的参数一致
此时发射信号的时候,就可以给信号函数传递实参,与之对应的这个参数就会被传递到对应的槽函数中.
举个例子,有参数的信号和槽,进行点击哪个按钮执行哪个动作
// Widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 只是建立信号与槽的连接,并没有发出信号
// 无参的
//connect(this,&Widget::mySignal,this,&Widget::handlemySingnal);
// 带参数的
connect(this,&Widget::mySignal2,this,&Widget::handlemySingnal);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handlemySingnal(const QString& Text)
{
// this->setWindowTitle("处理自定义信号");
this->setWindowTitle(Text);
}
void Widget::on_pushButton_clicked()
{
// 发出信号
// 发送信号,可以在任何适合的代码中
// 按下按钮之后,则就进行发送自定义信号
//emit mySignal();// 其实emit在Qt5中现在啥都没做
// 真正的操作都包含在mySingal信号中
// mySignal();// 这种方式也是可以的
emit mySignal2("把标题设置为标题1");
}
void Widget::on_pushButton_2_clicked()
{
emit mySignal2("把标题设置为标题2");
}
// Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handlemySingnal(const QString& Text);// 处理信号的自定义槽函数
signals:// Qt会自己识别关键字。自定义信号
//void mySignal();
// 带参数的信号
void mySignal2(const QString& Text);
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
直观的思考,应该是要求信号的参数个数和槽的参数个数,严格一致~~此处为啥允许信号的参数比槽的参数多呢??
一个槽函数,有可能会绑定多个信号
如果我们严格要求参数个数一致,就意味着信号绑定到槽的要求就变高了换而言之,当下这样的规则,就允许信号和槽之间的绑定更灵活了更多的信号可以绑定到这个槽函数上了
槽和信号是可以多对多的关系
Qt 信号槽,connect 这个机制, 设想很美好的
1)解耦合.把触发 用户操作的控件 和 处理对应用户的操作逻辑 解耦合
2)“多对多” 效果
一个信号, 可以 connect 到多个槽函数上,一个槽函数,也可以被多个信号 connect.
6. 信号和信号槽存在的意义
7. 信号与槽断开连接
使用 disconnect 来断开信号槽的连接
disconnect 使用的方式和 connect 是非常类似的,
disconnect用的比较少的.
大部分的情况下,把信号和槽连上了之后,就不必管了
主动断开往往是把信号重新绑定到另一个槽函数上~
定义两个按钮,使之按下第一个按钮窗口标题会有变化,在按下第二个按钮直接,会断开按钮1和槽函数的联系,之后按钮1与另一个槽函数建立联系,之后按下按钮1窗口标题会发生与之前不一样的变化。
- 第一步,在这个页面进行,如图,在右键按钮2机型 转移槽 的操作
第二部:编写代码
// widget.cpp
#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,&QPushButton::clicked,this,&Widget::handleClick);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()// 原先的槽函数
{
this->setWindowTitle("修改窗口的标题");
}
void Widget::handleClick2()// 断开修改之后的槽函数
{
this->setWindowTitle("修改窗口的标题2");
}
// 按下按钮2,切换槽函数
void Widget::on_pushButton_2_clicked()
{
// 1.先断开当前的槽和信号的连接
// 如果没有disconnect,则就会构成一个信号绑定两个槽函数,触发信号的时候,两个槽函数都会执行
//disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick);
// 会造成一对多的关系
// 2.之后进行之后的槽和信号的连接,重新绑定信号槽
connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick2);
}
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
// 处理的自定义槽函数
void handleClick();// 原先的槽函数
void handleClick2();// 要修改的槽函数
private slots:
void on_pushButton_2_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
8. 用lambda表达式也可以定义槽函数
定义槽函数的时候,也是可以使用 lambda 表达式的!!
举例
// Widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 用lambda表达式进行编写槽函数
QPushButton* mybutton=new QPushButton(this);
mybutton->setText("按钮");
mybutton->move(200,200);
// 由于lambda表达式默认是不会有外层作用域的数据,要进行捕获列表捕获
connect(mybutton,&QPushButton::clicked,this,[=]{
qDebug()<<"lambda表达式被执行了";
this->move(100,200);
mybutton->move(500,500);
});
}
Widget::~Widget()
{
delete ui;
}
// Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
运行结果:
解决lambda表达式无法从上层获取作用域中的变量
指针变量按照值传递或者引用来传递,都无所谓
lambda 除了可以按照 值的方式来捕获变量 [=] 还可以按照引用的方式来捕获 [&](Qt 中很少这么写)捕获到的变量一般就是各种控件的指针。
如果按引用,还得更关注这个引用的变量本身的生命周期
9. 查看内置信号和槽
系统⾃带的信号和槽通常是通过" Qt 帮助⽂档"来查询
10. 总结
- 信号槽是啥
1)信号源
2)信号的类型
3)信号的处理方式- 信号槽 使用
connect- 如何查阅文档.
一个控件,内置了哪些信号,信号都是何时触发
一个控件,内置了哪些槽,槽都是什么作用.
很有可能需要的信号槽,还得到这个类的父类/爷爷类/祖宗类去进行査询~~- 自定义槽函数
本质上就是自定义一个普通的成员函数
还可以让 Qt Creator 自动生成.(虽然没有显式 connect,但是可以通过函数名字特定规则来完成自动连接)- 自定义信号
信号本质就是成员函数.(函数的定义是 Qt 自己生成的,咱们只需要写函数声明)signals: 自定义关键字中,
emit 来完成信号的发射(emit 也可以省略)- 信号和槽还可以带有参数.发射信号的时候,把参数传递给对应的槽信号的参数和槽的参数要一致
1)类型匹配
2)个数,信号的参数要多于槽的参数- 信号槽存在的意义
解耦合
多对多效果.(非常类似于 mysql 中的多对多的)
演示了信号槽多对多连接的情况~~- disconnect 使用方式
- lambda 表达式, 简化槽函数的定义