QT信号与槽——观察者模式——回调函数
1、QT信号与槽机制
1.1信号本质
信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候 Qt 对应的窗口类会发出某个信号。比如:信号的本质是事件
按钮单击、双击 |
---|
窗口刷新 |
鼠标移动、鼠标按下、鼠标释放 |
键盘输入 |
在QT中信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测。
1.2槽的本质
在 Qt 中槽函数是一类特殊的功能的函数,在编码过程中也可以作为类的普通成员函数来使用。之所以称之为槽函数是因为它们还有一个职责就是对 Qt 框架中产生的信号进行处理。
比如:和朋友一起玩游戏(csgo),朋友说:我要丢闪了,于是我背身一下防闪;
实例对象 | 角色 | 描述 |
---|---|---|
朋友 | 信号发出者 | 信号携带的信息: 我要丢闪了 |
我 | 信号接收者 | 处理朋友发射的信号: 背身 |
在Qt中槽函数的所有者也是某个类的实例对象。
1.3信号和槽关系
在 Qt 中信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起,在 Qt 中我们需要使用 QOjbect类中的 connect 函数进二者的关联。
QMetaObject::Connection QObject::connect(
const QObject *sender, PointerToMemberFunction signal,
const QObject *receiver, PointerToMemberFunction method,
Qt::ConnectionType type = Qt::AutoConnection);
/*参数:
- sender: 发出信号的对象
- signal: 属于sender对象, 信号是一个函数, 这个参数的类型是函数
指针, 信号函数地址
- receiver: 信号接收者
- method: 属于receiver对象, 当检测到sender发出了signal信号,
receiver对象调用method方法,信号发出之后的处理动作*/
// 参数 signal 和 method 都是函数地址, 因此简化之后的 connect() 如下:框架自动调用
connect(const QObject *sender, &QObject::signal,
const QObject *receiver, &QObject::method);
2、标准信号槽
QT自带的一些信号与槽函数
3、自定义信号槽
Qt 框架提供的信号槽在某些特定场景下是无法满足我们的项目需求的,因此我们还设计自己需要的的信号和槽,同样还是使用 connect () 对自定义的信号槽进行连接。
如果想要在QT类中自定义信号槽, 需要满足一些条件, 并且有些事项也需要注意:
注意 |
---|
要编写新的类并且让其继承Qt的某些标准类 |
这个新的子类必须从QObject类或者是QObject子类进行派生 |
在定义类的头文件中加入 Q_OBJECT 宏 不加无法使用QT的信号槽机制 |
// 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏:
class MyMainWindow : public QWidget
{
Q_OBJECT
......
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
/* 引入 QPushButton */
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
/* 声明一个信号,只需声明,无需定义 */
void pushButtonTextChanged();
};
#endif // MAINWINDOW_H
3.1自定义信号
信号是类的成员函数,返回值必须是 void 类型,信号的名字可以根据实际情况进行指定,参数可以随意指定, 信号也支持重载,信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字,信号函数只需要声明, 不需要定义(没有函数体实现),在程序中发射自定义信号: 发送信号的本质就是调用信号函数,习惯性在信号函数前加关键字: emit, 但是可以省略不写,emit只是显示的声明一下信号要被发射了, 没有特殊含义,底层 emit == #define emit
// 举例: 信号重载
// Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以)
class Test : public QObject
{
Q_OBJECT
signals:
void AA();
// 参数的作用是数据传递, 谁调用信号函数谁就指定实参
// 实参最终会被传递给槽函数
void AA(int a);
};
3.2自定义槽
自定义槽注意事项 |
---|
返回值必须是 void 类型 |
槽也是函数, 因此也支持重载 |
槽函数需要指定多少个参数, 需要看连接的信号的参数个数 |
槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数 信号函数: void AA(int a, double b); 槽函数: void AA(int a, double b); 槽函数的参数应该和对应的信号的参数个数,从左到右类型依次对应 信号的参数可以大于等于槽函数的参数个数 == 信号传递的数据被忽略了 信号函数: void AA(int a, double b); 槽函数: void AA(int a); |
Qt中槽函数的类型是多样的 Qt 中的槽函数可以是类的成员函数、全局函数、静态函数、Lambda表达式(匿名函数) |
槽函数可以使用关键字进行声明: slots (Qt5中slots可以省略不写) public slots: private slots: –> 这样的槽函数不能在类外部被调用 protected slots: –> 这样的槽函数不能在类外部被调用。 |
// class A
class A : public QObject
{
Q_OBJECT
public:
explicit A(QObject *parent = nullptr);
signals:
void sig(); // 不能表达出想要吃什么
void sig(QString msg); // 可以通过参数表达想要吃什么
};
// class B
class B : public QObject
{
Q_OBJECT
public:
explicit B(QObject *parent = nullptr);
public slots:
// 槽函数
void slo(); // 不能知道信号发出者要吃什么
void slo(QString msg); // 可以知道信号发出者要吃什么
};
4、信号槽扩展
信号与槽可以多对一,也可以一对多
QT4连接方式
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
QT5及以上连接方式
connect(const QObject *sender, &QObject::signal,
const QObject *receiver, &QObject::method);
用QT5的连接方式时要注意槽函数重载问题,使用函数指针,比如:
// 定义函数指针指向重载的某一个具体的信号地址
void (Me::*mysignal)(QString) = &Me::eat;
// 定义函数指针指向重载的某一个具体的槽函数地址
void (Me::*myslot)(QString) = &Me::hungury;
// 使用定义的函数指针完成信号槽的连接
connect(&m, mysignal, &m, myslot);
最好使用QT5的连接方式,因为这样能检查出错误,QT4的使用宏定义把函数名变为字符串,即使写错了也不会报错;