信号和槽是 Qt 独有的一种机制,他让窗口的各种消息处理简化到极致,常规情况下我们相应某窗口(控件)的点击时都需要自己投递消息到框架中,由框架的消息队列投递给不同的窗口消息处理函数来处理。如果使用信号和槽,需要声明信号、定义槽函数、绑定信号和槽、发射信号就可以完成上述功能,代码简单容易理解,逻辑简单易懂。信号和槽的大致实现图如下:
【信号和槽使用规则和注意事项】 定义信号和槽:
- 信号和槽机制,是Qt的拓展,使程序员可以决定信号函数的调用目标
- 信号和槽只有Qt对象才能拥有(QObject类或QObject的子类才能定义信号和槽函数)
- 定义了信号和槽的Qt类,必须以 Q_OBJECT 宏开始,其内部是初始化信号和槽的环境
- 信号函数,定义在类的 signal 标识符保留字下,是Qt内部自己封装的功能,只有Qt Creator才识别,其他环境是不识别的,并且信号函数不需要实现,只需定义
- 槽函数,定义在类的 slot 标识符保留字下,也一样是Qt内部自己封装的,槽函数必须要实现
连接和调用:
- 连接信号和槽,使用 Object 类或 Object 子类的静态成员函数 connect 来连接信号和槽
- 发射(调用)信号函数,要使用 emit 保留字,emit 同样是Qt内部自己封装的,其他编译器并不能识别
注意事项:
- connect函数的第二个和第四个参数都是char*类型,需要使用SIGNAL和SLOT宏将带有括号的函数名转换为char*
- 信号和槽函数的参数个数最好保持一致,如果信号函数参数少于槽函数参数,那么程序会崩溃,因为槽不知道去哪取多出来的参数
- 信号函数可以和多个槽函数相连,当信号触发后,多个槽函数都会执行,但是哪一个优先执行,Qt并没有保障
- 一个槽函数可以被多个信号函数连接,这样多个信号会触发同一个槽函数
- 信号函数可以和信号函数连接,相当于一个传递者,两个信号都会调用同一个槽函数
- 信号和槽的参数有限制,限制比较多,比较明显的就是模版类对象是无法做参数的,如果需要传递比较特殊的数据类型,可以将数据先封装为结构体,然后调用 qRegisterMetaType<类型>(); 来注册结构体类型就可以通过信号和槽函数的参数传递了
总结:
- 信号和槽都在 QObecjt 类或子类下
- 三个处理宏 Q_OBJECT SIGNAL SLOT
- 三个保留字 signal slot emit
- 一个连接函数 QObject::connect
【手写示例代码】
代码中包含两个类和一个main.cpp文件,是将上面图中表示的情况编写为了代码,工有5个文件:
- csignal.h:信号类
- csignal.cpp:信号类
- cslot.h:槽类
- cslot.cpp:槽类 槽函数实现
- main.cpp:创建两个类并连接信号和槽
#ifndef CSIGNAL_H
#define CSIGNAL_H
#include <QObject>
// 信号和槽只有Qt对象才能拥有(QObject类或QObject的子类才能定义信号和槽函数)
class CSignal : public QObject
{
// 定义了信号和槽的Qt类,必须以 Q_OBJECT 宏开始,其内部是初始化信号和槽的环境
Q_OBJECT
public:
explicit CSignal(QObject *parent = 0);
signals:
void signalTest();
public slots:
};
#endif // CSIGNAL_H
代码语言:javascript复制#include "csignal.h"
CSignal::CSignal(QObject *parent) : QObject(parent)
{
}
代码语言:javascript复制#ifndef CSLOT_H
#define CSLOT_H
#include <QObject>
#include <QDebug>
// 信号和槽只有Qt对象才能拥有(QObject类或QObject的子类才能定义信号和槽函数)
class CSlot : public QObject
{
// 定义了信号和槽的Qt类,必须以 Q_OBJECT 宏开始,其内部是初始化信号和槽的环境
Q_OBJECT
public:
explicit CSlot(QObject *parent = 0);
signals:
public slots:
// 槽函数定义
void slotTest();
};
#endif // CSLOT_H
代码语言:javascript复制#include "cslot.h"
CSlot::CSlot(QObject *parent) : QObject(parent)
{
}
// 槽函数实现
void CSlot::slotTest()
{
qDebug() << "slotTest running...";
}
代码语言:javascript复制#include <QCoreApplication>
#include "csignal.h"
#include "cslot.h"
int main(int argc, char* argv[])
{
QCoreApplication app(argc, argv);
CSignal* sig = new CSignal;
CSlot* slot = new CSlot;
// 连接 CSignal 类的信号到 CSlot 类的槽函数
app.connect(sig, SIGNAL(signalTest()), slot, SLOT(slotTest()));
// 发射信号,因为上面绑定了槽函数,所以相当于调用了 CSlot 类中的 slotTest 函数
emit sig->signalTest();
return app.exec();
}
【信号和槽在框架中的使用】
在 Qt 框架中,我们创建的一些由 Qt 已经实现过的窗口时,内置了许多已经写好的信号函数,比如 QLineEdit 控件,我们在写他的信号和槽连接函数时,就能看到 IDE 给我们提示的这么多的信号函数:
比如我们希望在 QLineEdit 控件中输入完文字按下回车后自动处理某些事情,我们就可以一处理将其 returnPressed() 信号函数与我们自定义的一个槽函数绑定在一起,如下:
代码语言:javascript复制#include "cwidget.h"
#include <QPushButton>
#include <QLineEdit>
#include <QDebug>
CWidget::CWidget(QWidget *parent) : QWidget(parent)
{
QLineEdit* lineEdit = new QLineEdit(this);
// 将信号连接到本类中的 returnSlot() 槽函数
connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnSlot()));
}
// 在 CWidget.h 的 slot: 关键字下声明槽函数,在此处实现
void CWidget::returnSlot()
{
qDebug() << "lineEdit returnPress...";
}
此时当我们在 QLineEdit 窗口上按下回车键的时候,Qt Creator 调试信息就会输出 lineEdit returnPress…:
同样,按钮等窗口控件也都可以实现如上要求,系统都内置了很多信号函数,比如我们希望按一下按钮就退出程序,那么可以如下这样实现:
代码语言:javascript复制#include "cwidget.h"
#include <QPushButton>
#include <QLineEdit>
#include <QDebug>
CWidget::CWidget(QWidget *parent) : QWidget(parent)
{
QLineEdit* lineEdit = new QLineEdit(this);
// 将信号连接到本类中的 returnSlot() 槽函数
connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnSlot()));
QPushButton* button = new QPushButton("exit", this);
// 将button内置的信号函数 clicked 与本类中提供的 close 槽函数绑定到一起
connect(button, SIGNAL(clicked()), this, SLOT(close()));
}
// 在 CWidget.h 的 slot: 关键字下声明槽函数,在此处实现
void CWidget::returnSlot()
{
qDebug() << "lineEdit returnPress...";
}
当我们点下 exit 按钮时,系统会调用 CWidget 类中的 close() 函数来退出窗口。这样的案例还有很多,大家可以自己在 Qt Creator 中编写代码时多多留意。