本节对应的视频讲解:B_站_链_接
https://www.bilibili.com/video/BV1gA4y1d7gz
上节课,我们讲解了标准信号槽,标准信号槽中,信号和槽函数,都是 Qt 框架定义好的。
Qt
还允许我们自定义信号和槽。
自定义信号和槽的条件:
- 自定义的类,要继承自
QObject
- 自定义的类,其中要声明一个宏
Q_OBJECT
只有满足了这两个条件才可以正常使用信号槽机制(当然,槽函数是全局函数、 Lambda
表达式等无需接收者的时候除外,后边讲解)。
1. 自定义信号槽案例
接下来,我们通过一个案例,演示自定义信号槽的使用。
案例:“长官” (Commander
)发送一个 “冲” (go
) 的信号,然后 “士兵" (Soldier
)执行“ 战斗” (fight
) 的槽函数
1.1 创建 Commander 类
在左侧项目文件名上右键 -> 添加新文件:
指定类名和父类
点击完成,即可添加cpp和.h文件到项目中
创建完成之后的 commander.cpp
和 commander.h 文件
,内容如下:
// commander.h
#ifndef COMMANDER_H
#define COMMANDER_H
#include <QObject>
// 1.自定义的类,需要继承自 QObject 类
class Commander : public QObject
{
// 2.并且添加 Q_OBJECT 宏,才能正常使用 Qt 的信号和槽机制
Q_OBJECT
public:
explicit Commander(QObject *parent = nullptr);
// 3.在 signals 后面添加自定义的信号即可
signals:
};
#endif // COMMANDER_H
// commander.cpp
#include "commander.h"
Commander::Commander(QObject *parent)
: QObject{parent}
{
}
1.2 添加自定义信号
在 signals
下面添加自定义的信号即可
class Commander : public QObject
{
Q_OBJECT
public:
explicit Commander(QObject *parent = nullptr);
signals:
// 1.信号只需声明,无需实现
// 2.信号返回值为 void
void go();
};
1.3 添加 Soldier 类
按照同样的方法,添加 Soldier
类,创建完成之后的 soldier.h
和 soldier.cpp
文件,内容如下:
// soldier.h
#ifndef SOLDIER_H
#define SOLDIER_H
#include <QObject>
class Soldier : public QObject
{
Q_OBJECT
public:
explicit Soldier(QObject *parent = nullptr);
signals:
};
#endif // SOLDIER_H
// soldier.cpp
#include "soldier.h"
Soldier::Soldier(QObject *parent)
: QObject{parent}
{
}
1.4 添加自定义槽
Soldier
士兵类,需要实现一个 fight
的槽函数
class Soldier : public QObject
{
Q_OBJECT
public:
explicit Soldier(QObject *parent = nullptr);
signals:
// 1.通常将槽函数添加到 slots 后面
// 这个 slots 也可以不写。不过建议写上,以指明这是一个槽函数
// pulic,表示槽函数既可以在当前类及其子类的成员函数中调用,也可以在类外部的其它函数(比如 main() 函数)中调用
public slots:
// 2.槽函数的返回值和参数,要和信号保持一致
// 由于信号无返回值,因此槽函数也无返回值
// 由于信号无参数,因此槽函数也无参数
void fight();
};
在 soldier.h
文件中有了槽函数的声明,还需要在 soldier.cpp
中实现
可以在槽函数声明处,直接按 alt + enter
快捷键,快速生成函数定义
void Soldier::fight()
{
qDebug() << "fight";
}
1.5 连接自定义的信号和槽
信号和槽都已经定义完毕, 接下来就可以进行连接了
在 mainwindow.cpp
中,定义 Commander
和 Soldier
类的实例,并建立信号和槽的连接,如下:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1. 创建两个类的实例
Commander commander;
Soldier soldier;
// 2. 建立信号和槽的连接
connect(&commander, SIGNAL(go()), &soldier, SLOT(fight()));
// 3. 发送信号
// emit 可省略
/*emit*/commander.go();
}
这样,在程序执行后,就可以在【应用程序输出】窗口看到槽函数执行的结果了:
1.6 信号和槽的重载
我们知道,信号和槽的本质就是函数,是函数就可以重载,因此,我们可以重载同名的信号,和重载同名的槽函数。
仍然以 Commander
和 Soldier
为例:
在 commander.h
中添加重载的 go
信号:
class Commander : public QObject
{
Q_OBJECT
public:
explicit Commander(QObject *parent = nullptr);
signals:
void go();
void go(QString);
};
在 soldier.h
中添加重载的fight
槽函数:
class Soldier : public QObject
{
Q_OBJECT
public:
explicit Soldier(QObject *parent = nullptr);
signals:
public slots:
void fight();
void fight(QString);
};
在 soldier.cpp
中实现重载的fight
槽函数:
void Soldier::fight(QString s)
{
qDebug() << "fight for" << s;
}
接下来在mainwindow.cpp
中同时发送重载的两个go
信号:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1. 创建两个类的实例
Commander commander;
Soldier soldier;
// 2. 建立信号和槽的连接
connect(&commander, SIGNAL(go()), &soldier, SLOT(fight()));
connect(&commander, SIGNAL(go(QString)), &soldier, SLOT(fight(QString)));
// 3. 发送信号
commander.go();
commander.go("freedom");
}
此时执行程序,就可以在【应用程序输出】窗口看到两个重载的槽函数执行的结果了:
2. 信号槽总结
2.1 使用信号和槽的条件
如果要使用信号和槽,需要满足如下两个条件
- 自定义的类,要继承自
QObject
- 自定义的类,其中要声明一个宏
Q_OBJECT
只有满足了这两个条件才可以正常使用信号槽机制。
2.2 信号
- 无需实现
信号的本质是函数,并且只需要声明,不需要实现; - 可以重载
信号本质是函数,因此可以重载; - 信号声明在类头文件的
signals
域下; - 信号返回值类型为
void
,参数的类型和个数不限; - 信号可以使用
emit
关键字发射,emit
也可以省略;
2.3 槽
- 需要实现
槽的本质是函数,需要实现; - 可以重载
槽本质是函数,因此可以重载; - 槽函数用
slots
关键字修饰(其实在Qt5
中可以省略slots
关键字,下一节会讲解); - 返回值
槽函数的返回值,要和信号保持一致。由于信号的返回值为void
,因此槽函数的返回值也是void
- 参数
槽函数的参数个数要<=
信号的参数个数
也就是说:可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)
本节对应的视频讲解:B_站_链_接
https://www.bilibili.com/video/BV1gA4y1d7gz