本节对应的视频讲解:B_站_链_接
https://www.bilibili.com/video/BV1mN4y137H6
信号和槽要建立连接,本质上是通过 `connect` 函数来连接实现的。
但是从写法或者操作上来说,有多种方式,以下总结了 5
种方式:
SIGNAL
/SLOT
(Qt4
)- 函数地址(
Qt5
) UI
设计师界面 - 转到槽UI
设计师界面 - 信号槽编辑器lambda
表达式
大家可根据自己的喜好自行选择。
接下来通过一个案例,来演示这 5
种使用方法:
首先,我们先把这个界面,快速地搭建起来
快速新建一个基于 QMainWindow
的工程:
切换到 mainwindow.ui
文件,按照如下方法,设计好界面:
- 修改窗口标题为 “信号槽的 5 种连接方式”
- 拖拽 5 个
QPushButton
- 修改按钮的
name:btnMax
、btnNormal
、btnMin
、btnClose
、btnSetWindowTitle
- 布局窗口:垂直布局,以使得布局自适应窗口大小的变化
- 修改按钮显示的文字,如上
- 设置按钮字体大小为
15
经过以上几步,就完成了界面的布局,如下:
接下来就开始实现 5 种连接方式。
1. SIGNAL/SLOT(Qt4)
上一节的就是使用的这个方式
通过 SIGNAL/SLOT
这两个宏,将函数名以及对应的参数,转换为字符串,这是 Qt4
中使用的方式,当然在 Qt5
中也是兼容支持它的:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
其中:
sender
:信号发送者SIGNAL(signal())
:发送的信号。SIGNAL 宏将信号转换成字符串receiver
:信号接收者SLOT(slot())
:槽函数。SLOT
宏将槽函数转换成字符串
这种方式,编译器不会做错误检查,即使函数名或者参数写错了,也可以编译通过,这样就把问题留在了运行阶段。
而我们编程开发的一个原则是尽可能早地发现并规避问题,因此这种方式不被推荐。
1.1 实现窗口最大化
下面通过这种方式,实现点击按钮,最大化窗口
在 mainwindow.cpp
的构造函数中,使用如下方式连接信号和槽:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1.使用 SIGNAL/SLOT 的方式连接信号和槽
connect(ui->btnMax, SIGNAL(clicked()), this, SLOT(showMaximized()));
}
此时,运行程序,点击按钮就可以最大化窗口了!
1.2 编译不检查
刚才说过,如果函数的名字写错了,在编译时不会报错,比如将 showMaximized 不小心写成了 showMaximize
点击【构建】菜单->【重新构建】,在【编译输出】窗口并不会报错
而在运行时,在【应用程序输出】窗口会看到报错,如下:
22:24:45: Starting E:\qt_project\build-HowToConnectSignalAndSlot-Desktop_Qt_5_15_2_MinGW_32_bit-Debug\debug\HowToConnectSignalAndSlot.exe...
QObject::connect: No such slot MainWindow::showMaximize() in ..\HowToConnectSignalAndSlot\mainwindow.cpp:10
QObject::connect: (sender name: 'btnMax')
QObject::connect: (receiver name: 'MainWindow')
2. 函数地址(Qt5)
这种方式中,信号和槽都使用函数的地址,如下:
connect(sender, &Sender::signal, receiver, &Receiver::slot);
其中:
sender
:信号发送者&Sender::signal
:发送的信号receiver
:信号接收者&Receiver::slot
:槽函数
这种方式,编译时就会对函数类型,参数个数做检查。
2.1 实现窗口正常显示
下面通过这种方式,实现点击按钮,正常化显示窗口
在 mainwindow.cpp
的构造函数中,使用如下方式连接信号和槽:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1.使用 SIGNAL/SLOT 的方式连接信号和槽
connect(ui->btnMax, SIGNAL(clicked()), this, SLOT(showMaximized()));
// 2.使用函数地址的方式连接信号和槽
connect(ui->btnNormal, &QPushButton::clicked, this, &QMainWindow::showNormal);
}
此时,运行程序,点击按钮就可以正常化显示窗口了!
2.2 编译检查
如果函数的名字写错了,在编译时就会报错,比如将 showNormal
不小心写成了 showNorma
,少写一个字母 L
,
在编辑器中就会飘红报错,如下:
mainwindow.cpp:14:58: No member named 'showNorma' in 'QMainWindow'; did you mean 'QMainWindow::showNormal'? (fix available)
qwidget.h:486:10: 'QMainWindow::showNormal' declared here
当然了,点击【构建】菜单->【重新构建】,在【编译输出】窗口也会会报错,如下:
..\HowToConnectSignalAndSlot\mainwindow.cpp: In constructor 'MainWindow::MainWindow(QWidget*)':
..\HowToConnectSignalAndSlot\mainwindow.cpp:14:71: error: 'showNorma' is not a member of 'QMainWindow'
connect(ui->btnNormal, &QPushButton::clicked, this, &QMainWindow::showNorma);
^~~~~~~~~
mingw32-make[1]: *** [Makefile.Debug:469: debug/mainwindow.o] Error 1
可见,使用这种方式,错误在代码编辑,程序编译阶段就会暴露出来,及早解决掉它。
这是 Qt
推荐的一种连接信号槽的方式!
3. UI设计师界面-转到槽
下面使用这种方式,实现点击 btnMin
按钮,最小化显示窗口
在 UI
设计师界面,右键单击 btnMin
,然后选择【转到槽…】,弹出如下窗口:
选择 clicked()
,即可生成并跳转到槽函数,即可在 mainwindow.h
和 mainwindow.cpp
中生成对应的代码,如下:
// mainwindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
...
private slots:
void on_btnMin_clicked();
};
// mainwindow.cpp
void MainWindow::on_btnMin_clicked()
{
this->showMinimized();
}
此时会根据按钮的 name
自动生成对应的槽函数,对应关系为:
按钮的名字:btn1
槽函数的名字为:on_btn1_clicked
注意:如果修改了按钮的 name
,那么槽函数的名字也要随之修改。
4. UI设计师界面-信号槽编辑器
下面使用这种方式,实现点击 btnClose
按钮,关闭窗口
进入到 UI
设计师界面,【View
】菜单 ->【视图】->【Signal & Slots Editor
】,在打开的信号槽编辑器中,点击绿色的加号+,
就可以连接信号和槽了:
此时,我们的代码文件并没有修改,而是修改了 mainwindow.ui
文件,如下:
我们在 1.4
小节时详细讲解过这个 ui
文件如何转换为编译器可以编译的 .h/.cpp
文件
没有看之前的视频的小伙伴,可以去观看《1.4 项目构建流程》
通过之前的讲解,我们知道,这个 ui
文件,最终会被转换为 ui_mainwindow.h
文件,打开就可以看到,转换后还是通过 connect
来连接的信号和槽,如下:
此时,运行程序,点击 btnClose
按钮,就可以关闭窗口了!
5. Lambda 表达式
槽函数还可以直接写成 lambda
表达式的形式。
C++11
引入了 lambda
表达式,用于定义并创建匿名的函数对象,可以使代码更加的简洁
Qt
是基于 C++
的一个 GUI
框架,它完全支持 C++
的语法,因此在 Qt
中也是可以使用 lambda
表达式的。
学习 Qt
多多少少都需要了解一些 C++
的语法
不过如果你对 C++
中的 lambda
表达式不了解,也不用担心,这里我们先复习一下 C++
中 lambda
表达式的使用
lamda
表达式在现代语言中,非常的普遍。lamda
表达式简化了我们的一些语法,使得代码的表达更加的清晰,便于书写与阅读。
一些现代语言,比如 java
、python
、kotlin
等语言都是支持 lambda
表达式的
5.1 复习 lambda 表达式
C++
中的 lambda
表达式,其实就是匿名函数,语法如下:
[capture](parameters) option -> return-type { body }
其中包含 5 个部分:
-
capture
:捕获列表,可选捕捉列表总是出现在
lambda
表达式的开始。实际上,[]
是lambda
引出符,编译器根据该引出符判断接下来的代码是否是lambda
表达式。捕捉列表能够捕获上下文中的变量,以在
lambda
表达式内使用,主要有如下几种情况:# 不捕获任何变量 [] # 按引用捕获外部作用域中所有变量,在 lambda 表达式内使用 [&] # 按值捕获外部作用域中所有变量,在 lambda 表达式内使用 # 按值捕获的变量,在 lambda 表达式内是只读的,不能修改赋值 [=] # 捕获外部作用域中所有变量,其中foo变量按引用捕获,其他的按值捕获 [=, &foo] # 按值捕获bar变量,同时不捕获其他变量 [bar] # 捕获当前类中的 this 指针 # 捕获了 this 就可以在 lambda 中使用当前类的成员变量和成员函数。 # 如果已经使用了 & 或者 =,就默认添加此选项。 [this]
-
parameters
:参数列表,可选 -
option
:函数选项,可选 -
return-type
:返回值类型,可选。没有返回值的时候也可以连同符号->
一起省略 -
body
:函数体
接下来,在 mainwindow.cpp
的构造函数中,逐一演示这 5 个部分的使用
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
...
// 演示lambda表达式
// 3.1 匿名函数的定义
#if 0
[]() {
qDebug() << "lambda...";
};
#endif
// 3.2 匿名函数的调用
#if 0
[]() {
qDebug() << "lambda...";
}();
#endif
int a =10;
// 3.3 不捕获任何变量
// Variable 'a' cannot be implicitly captured in a lambda with no capture-default specified
#if 0
[]() {
qDebug() << a;
}();
#endif
// 3.4 按引用捕获
#if 0
[&]() {
qDebug() << a++; // 10
}();
qDebug() << a; // 11
#endif
// 3.5 按值捕获
// 按值捕获的变量,在 lambda 表达式内是只读的,不能修改赋值
#if 0
[=]() {
// Cannot assign to a variable captured by copy in a non-mutable lambda
qDebug() << a++;
}();
#endif
// 3.6 按值捕获 + mutalbe 选项
// 添加 mutable 选项,就可以在 lambda 内修改捕获的变量了
// 并且=这种方式,是按值传递的,里面的修改,不会影响外边。
#if 0
[=]() mutable {
qDebug() << a++; // 10
}();
qDebug() << a; // 10
#endif
// 3.7 参数
// 添加 mutable 选项,就可以在 lambda 内修改捕获的变量了
// 并且=这种方式,是按值传递的,里面的修改,不会影响外边。
#if 0
[](int x, int y) {
qDebug() << x + y; // 3
}(1, 2);
#endif
// 3.8 返回值
// 返回值可以省略,编译器会自动推断 lambda 表达式的返回值类型
// 返回值省略时,也可以连同符号`->`一起省略
#if 0
int sum = [](int x, int y) -> int {
return x + y;
}(1, 2);
qDebug() << sum; // 3
#endif
#if 1
int sum = [](int x, int y) {
return x + y;
}(1, 2);
qDebug() << sum; // 3
#endif
}
5.2 槽函数使用 lambda 表达式
有了以上 lambda
表达式的基本知识,将它作为槽函数,就水到渠成了,如下:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1.使用 SIGNAL/SLOT 的方式连接信号和槽
connect(ui->btnMax, SIGNAL(clicked()), this, SLOT(showMaximized()));
// 2.使用函数地址的方式连接信号和槽
connect(ui->btnNormal, &QPushButton::clicked, this, &::QMainWindow::showNormal);
// 3.UI设计师界面-转到槽
// 直接在UI界面,鼠标点击完成信号槽的连接
// 4.UI设计师界面-信号槽编辑器
// 直接在UI界面,鼠标点击完成信号槽的连接
// 5. 使用lambda 表达式做槽函数
connect(ui->btnSetWindowTitle, &QPushButton::clicked, this, [this]() {
this->setWindowTitle("[连接信号槽的 5 种方式]");
});
}
此时,运行程序,点击 btnSetWindowTitle
按钮,就可以修改窗口的标题了!
本节对应的视频讲解:B_站_链_接
https://www.bilibili.com/video/BV1mN4y137H6