Qt教程-信号槽

要使用 Qt 就不得不提到信号槽,信号槽(signal 和 slot)是 Qt 的核心机制之一。
我们都知道界面是通过用户给出的操作进行反馈的。
假设 Windows 系统上的一个窗口上存在一个按钮,当用户在这个按钮上按下鼠标左键后。
Windows 会向这个窗口发送一条 WM_LBUTTONDOWN 消息。
当用户抬起鼠标左键时 Windows 会向窗口发送一条 WM_LBUTTONUP 消息。
程序通过一个线程循环处理这些窗口上发生的消息作出响应。最终形成的效果可能是,用户单击了一个按钮,程序弹出了一个提示信息。
通过循环响应各种消息程序就能作出各种各样的反映。在传统的开发模式中开发者需要自己创建消息循环,处理各种消息,然后再调用对应的处理函数。不得不说这样实在很麻烦。
信号槽这个机制本身有点像处理消息队列,有所不同的是开发者无需处理自己不关心的事件。当一个事件发生的时候(比如:鼠标点击事件)会产生一个信号,Qt会寻找和这个信号关联在一起的函数,并调用这个函数,通过这样的方式完成响应。
在这样的流程下开发者要做的事情就很简单了,把响应操作的函数和信号关联起来就行了。和传统的处理消息队列相比,不用自行维护一个消息处理循环,不用考虑线程阻塞等一些问题,只要专注与自己想处理的信号就可以了,这样无疑方便了开发者。
信号槽的使用非常的简单,使用 connect 函数连接信号和处理函数就可以了。连接之后一旦有信号发生则会自动调用处理函数。
我们可以看到例子 hello_world.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <QApplication>
#include <QLabel>
#include <QPushButton>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QLabel label("Hello, world");
QLabel label2("Hello, world2");
QPushButton button("Show Hello");

QObject::connect(&button, &QPushButton::clicked, [&](){label2.show();});
//这里使用了Lambda 表达式,Lambda表达式是C++11中支持的新特性
QObject::connect(&button, SIGNAL(clicked()),&label,SLOT(show()));
button.show();
return app.exec();
}

编译运行后运行能看到一个按钮,按下按钮后会产生如下图的效果。 QApplication 类我们前面介绍过用来管理GUI程序的控制流和主设置。它包含主事件循环,用来处理窗口的各种事件。
QLabel 是一个标签控件,标签控件通常用于显示各种信息。
QPushButton 则是按钮控件,界面上的各种按钮由此控件提供。 QObject::connect 是信号槽的连接函数。
我们可以看到,代码中定义了2个 QLabel 控件,分别是 label 和 label2,分别使用 “Hello, world” 和 “Hello, world2” 这两个字符串进行初始化。随后我们还定义了一个 QPushButton 控件,QPushButton 是按钮控件用来提供界面上的各种按钮。我们使用 Show Hello 初始化 QPushButton 类,最终的效果就是按钮上显示的文字为 Show Hello。
随后我们调用了2次 connect 函数,QObject::connect 函数的作用是吧信号和槽关联起来。
connect 函数有下面几种重载

1
2
3
4
5
6
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection)
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) const
QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)

我们简化一下,常用的大约有这样的两种形式。

1
2
connect(sender,signal,function);
connect(sender,signal,receiver,slot);

我们这里的调用同样也符合这两种形式,本例中 sender 都为 button 对象,也就是按钮。
signal 在本例中都是 QPushButton::clicked。 QPushButton::clicked 这个信号在被鼠标左键点击的时候会发出。本例中第一次调用 connect 符合 connect(sender,signal,function) 这种形式,Lambda 表达式可以当成是一个没有名字的函数。
第二次的调用符合connect(sender,signal,receiver,slot)这种形式,发出的信号由label对象作为接收者,最后调用show这个函数。
一个信号可以连接多个槽函数,所以最终会看到启动了2个新窗口。前面的例子中演示了 connect 的两种用法。SIGNAL 和 SLOT 宏作用是把函数转换成字符串。
在 Qt5 之前这样是标准做法,但是编译器无法检查字符串的合法性,容易出错,故新版本中推荐直接 connect 而尽可能不要使用 SIGNAL 和 SLOT 宏。