前言
在 C++ 中,对象与对象之间产生联系要通过调用成员函数的方式。但是在 Qt中,Qt提供了一种新的对象间的通信方式,即信号和槽机制。在GUI编程中,通常希望一个窗口部件的一个状态的变化会被另一个窗口部件知道,为了实现这种效果且取代老式的回调函数,信号和槽机制应运而生,Qt通过 QObject 提供信号和槽的功能。
信号和槽的核心原理很简单,当某个事件发生之后,如按钮检测到自己被单击了一下,它就会广播出一个信号。如果有对象对这个信号感兴趣,就使用连接函数,将想要处理的信号和自己的一个函数(称为槽函数)进行绑定并处理这个信号。也就是说,当信号被发出时,与之连接的槽即函数会自动被回调。
信号和槽的使用
槽的本质就是类的成员函数,其参数可以是任意类型,可以是虚函数,可以被重载。槽通常和信号连接在一起,当信号被发出时,与这个信号连接的槽函数就会被调用,其语法如下。
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
具体参数如下:
- sender:发出信号的对象。
- signal:发送对象发出的信号。
- receiver:接收信号的对象。
- slot:接收对象在接收到信号之后所需要调用的函数。
参数中的 sender 就是指向发送信号的对象的指针,receiver 是指向包含槽函数的对象的指针。signal是被发送的信号,slot是接收信号后调用的槽函数,均为不带参数的函数名。SIGNAL()和SLOT()会将其参数转换为字符串,即将函数名转换为字符串,将这个字符串传入 connect()中。
信号和槽的连接比较随意,一个信号可以连接多个槽。
connect(sender, SlGNAL(sianal), receiverA, SLOT(slotA));
connect(sender, SlGNAL(signal), receiverB, SLOT(slotB));也可以多个信号连接同一个槽。
connect(senderA, SIGNAL(signalA), receiver, SLOT(slot));
connect(senderB, SlGNAL(signalB), receiver, SLOT(slot));一个信号还可以连接另外一个信号。
connect(sender, SIGNAL(signalA), receiver, SlGNAL(signalB));当 sender对象发送信号给signalA时,触发receiver对象发送信号给signalB。同时,信号和槽之间的连接可以被移除。
disconnect(sender, SlGNAL(signal),receiver, SLOT(slot));
Qt 信号和槽机制的优缺点如下:
- Ot的信号和槽机制的引用可减少程序员编写的代码量。
- Qt的信号可以对应多个槽(它们的调用顺序随机),也可以多个槽映射一个信号
- Qt的信号和槽的建立与解除绑定十分自由。
- 信号和槽同真正的回调函数比起来,时间的耗损还是很大的,在嵌入式实时系统中应当慎用。
- 信号和槽的参数限定很多,如不能携带模板类参数、不能出现宏定义等。
用一个例子来解释一下信号和槽的机制:
新建一个基类 QWidget的 Qt 图形应用项目。
在widgt.h文件中:
#ifndef WIDGT_H
#define WIDGT_H
#include<QPushButton>
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class widgt; }
QT_END_NAMESPACE
class widgt : public QWidget
{Q_OBJECT//只有继承了QObject类的类,才具有信号和槽;//宏QOBJECT是任何实现信号、槽或属性的强制性要求
public:widgt(QWidget *parent = nullptr);~widgt();
private:Ui::widgt *ui;QPushButton button;
};
#endif // WIDGT_H
在widgt.cpp文件中:
#include "widgt.h"
#include "ui_widgt.h"
widgt::widgt(QWidget *parent): QWidget(parent), ui(new Ui::widgt)
{this->resize(200,200);//修改默认的窗口尺寸button.setParent(this);// 绑定窗口和按钮button.setText("关闭窗口");// 按钮框中文本button.move(48,64);// 定义按钮的位置,以左上角为原点,px 为单位connect(&button,&QPushButton::pressed,this,&widgt::close);
}
widgt::~widgt()
{delete ui;
}
在main.cpp文件中:
#include "widgt.h"
#include <QApplication>
int main(int argc, char *argv[])
{QApplication a(argc, argv);widgt w;w.show();return a.exec();
}
在这些代码中,button是信号的发送方;信号为QPushButton基类定义的pressed信号;信号接收方为 widgt w,即在构造函数中写为 this;槽函数为基类QWidget定义的 close()函数。这样,在单击“close”按钮时,该按钮 button会发送一个pressed信号,pressed信号会触发w中继承的 close()函数,实现结束程序的功能。
自定义信号和槽函数
connect()函数可用来连接系统提供的信号和槽。但是,Qt的信号和槽机制并不是仅能使用系统提供的那部分,程序员也可设计自己的信号和槽。
首先,创建一个基于基类 QWidget的 Qt图形应用项目,添加一个新的类,类名为“Widget”继承于 Qt 的基类 QWidget。
在widgt.h文件中:
#ifndef WIDGET_H
#define WIDGET_H
#include <QLabel>
#include <QPushButton>
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{Q_OBJECT
public:Widget(QWidget *parent = nullptr);void MySlot();//定义~Widget();
private:Ui::Widget *ui;QPushButton button;QLabel label;
};
#endif // WIDGET_H
在widgt.cpp文件中:
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{this->resize(240,320);button.setParent(this);button.move(40,50);button.setText("更换内容");label.setParent(this);label.move(40,100);label.setText("更换前");connect(&button,&QPushButton::pressed,this,&Widget::MySlot);
}
void Widget::MySlot()
{label.setText("更换后");
}
Widget::~Widget()
{delete ui;
}
在main.cpp文件中
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();return a.exec();
}
结果:
自定义信号和槽函数的要点
自定义槽函数要点如下。
- 槽函数可以传入参数,但没有返回值。
- 在 Qt5 中,任何成员函数、静态成员函数、全局函数及 Lambda 表达式都可以作为槽函数。与信号函数不同,槽函数必须自己完成实现代码。槽函数就是普通的成员函数,因此也会受到public、private 等访问控制关键字的影响。( 如果信号是私有的,这个信号就不能在类的外面连接,而类中本来就可以直接传递,因此这种限制也就没有任何意义。)
自定义信号和槽需要注意的事项有以下几个。
- 发送者和接收者都需要 QObject的派生类(当然,槽函数是全局函数、Lambda 表达式等无须接收者的时候除外 )。
- 使用 signals 标记信号,信号是一个函数声明,返回 void,不需要实现函数代码。
- 使用 emit 在恰当的位置发送信号。
- 可以在 main.cpp 中使用QObject::connect()函数连接信号和槽函数。
- 任何成员函数、静态成员函数、全局函数及Lambda 表达式都可以作为槽函数。
Q_OBJECT
在Qt中,如果一个类要使用信号和槽的功能,就必须在其中声明Q_OBJECT,类的定义的第一行就写上了 Q_OBJECT。不管是不是使用信号和槽,都应该添加 O_OBJECT宏,这个宏为类提供了信号和槽机制、国际化机制以及Qt提供的不基于 C++ RTTI的反射能力。其他很多操作都会依赖于这个宏,即使类中不需要使用信号和槽,也需要添加这个宏,否则会出现编译错误。