Qt教程-自定义信号槽和信号槽的实现

我们说 Qt 不仅是一个 GUI 的库,可以说Qt是一套框架,除了提供 GUI 的功能 Qt 还提供了一系列的东西。我们之前提到的信号槽,也不仅仅只能在 GUI 程序中使用,信号槽是作为一种核心特性提供的,当然我们也可以注册自己的信号和槽函数。
有关语法我们可以直接看到 custom_signal 下面的 custom_signal.hpp 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ifndef __CUSTOM__SIGNAL_HPP__
#define __CUSTOM__SIGNAL_HPP__

#include <QObject>
#include <QPushButton>
class CCustom_Signal:public QObject
{
/*
所有QObject的派生类在官方文档中都推荐在头文件中放置宏Q_OBJECT。
如果要使用信号槽则必须添加Q_OBJECT宏。
这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI(Run-Time Type Information运行时类型检查)的反射能力(运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法)。

*/
Q_OBJECT
public:
void send(QPushButton * object)
{
//emit是QT特有的关键字,用来表示发送信号
emit clicked(object);
}
//signals标签也是QT扩展的关键字,用来表示下面声明的是信号
signals:
void clicked(QPushButton * object);

};

class CCustom_Slot:public QObject
{
Q_OBJECT

/*
public slots和private slots同样也是QT扩展的关键字,用来表示下面的都是槽函数。
public和private代表的含义和C++中类的public和private一样。有所不同的地方是,private slots也可以同样被connect连接触发,但是private slots不能被直接调用。
由于QT4的语法并没有编译时类型检查,所以private slots也可以同样被connect连接,但是在QT5语法中connect在编译时会进行类型检查,所以不能通过编译。对于这样的特性用户可以按照自己的需要进行处理。
*/
public slots:
void recv(QPushButton * object)
{
object->clicked();
}
};
#endif

这里我们定义了2个类,一个是 CCustom_Signal 其中包含了名叫 clicked的信号,叫 send 的信号发送函数。另一个是 CCustom_Slot 其中定义了一个叫recv的槽函数。
由于具体的介绍参见代码注释。
我们总结一下自定义信号和槽的步骤:
1、定义信号必须在一个类中,并且这个类必须是 QObject 的子类(也就是必须继承 QObject)。
2、类中定义信号之前,必须包含 Q_OBJECT 宏。
3、声明信号函数之前必须使用 signals 标签。
4、定义信号时我们只需要声明信号函数,我们不要自己定义信号函数。
我们不难发现一个问题,Qt 当中这些特有的关键字为什么能最终通过 gcc 这样的编译器进行编译,有些语法明明不符合 C++ 的标准。
当我们使用 Qt 进行开发时源代码交给标准 C++ 编译器(如 gcc )之前,需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc(Meta-Object Compiler,也就是“元对象编译器”)。moc 负责把这些扩展语法转换成标准的 C++ 语法,这个过程就像预编译处理。
在笔者的电脑上Qt的安装路径是 /opt/Qt5,moc 最后的路径是 /opt/Qt5/5.8/gcc_64/bin/moc,在调用 gcc 编译之前我们可以看到会先调用 moc 进行预编译处理。
使用 Qt 后在程序编译上的流程就和普通的 C++ 程序有些区别了,我们这里简单的梳理一下。
当我们在 Qt Creator 上按下编译按钮后会有下面的几个步骤
1、执行 qmake。qmake 会根据项目配置生成 makefile。这样我们在执行 make 的时候就可以自动进行 moc 等操作。
2、执行 moc 转换代码为标准的 C++ 代码。
3、由 C++ 编译器进行编译。
4、由 C++ 链接器进行链接。
当然,这里我们自定义了一个信号和槽一共两个类,那么要如何使用这两个类呢?这里我们也做一个对应的演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <QApplication>
#include <QLabel>
#include <QPushButton>
#include "custom_signal.hpp"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel label("Hello, world");
QPushButton button("Show Hello");

//QT5
QObject::connect(&button, &QPushButton::clicked, [&](){label.show();});
//QT4 语法
//QObject::connect(&button, SIGNAL(clicked()),&label,SLOT(show()));

CCustom_Signal csig;
CCustom_Slot cslot;
//QT4 语法
//QObject::connect(&csig, SIGNAL(clicked(QPushButton*)),&cslot,SLOT(recv(QPushButton*)));
//QT5
QObject::connect(&csig,&CCustom_Signal::clicked,&cslot,&CCustom_Slot::recv);

button.show();
csig.send(&button);

return app.exec();
}

要使用在头文件中定义的类,那么首先我们理所当然的要 include 对应的文件。
这里我定义了一个 csig 类并把 csig 对象中的 clicked 对象和 cslot 这个对象中的 recv 函数连接了起来。
recv 函数最后会调用传入的 QPushButton 对象的 clicked 函数,其实也就是向这个 QPushButton 对象发出 clicked 信号。在 Qt 中 emit 实际上是一个宏,但是这个宏是空的,没有做任何操作。emit 这个关键字只是用来明确标识是在发送信号。
最后我们看到我们使用了 csig 的 send 函数,并传入了 button 对象的引用。
也就是说这里实际上是向 button 对象发送了点击信号,所以当 exec 执行后就会弹出2个对话框。
如下图所示:

工程源代码下载地址: /uploads/public/qt_tutorial/custom_signal_6F457F93.7z