参考资料:
- Qt帮助文档中的《Signals & Slots》
作用
用于在对象之间建立通信。
当发生某个事件时,信号会被发出,槽函数会被调用,以响应某个信号。
信号和槽机制是类型安全的:信号的签名必须与槽的签名匹配(实际上,一个槽的签名可能比它接收到的信号的签名要短,它可以忽略信号传递过来的额外的参数)。
信号和槽是松耦合的:发出信号的类既不知道也不关心哪个槽会接收信号。
所有从QObject
或其子类(例如,QWidget)继承的类都可以包含信号和槽。
槽可以用来接收信号,但也是普通的成员函数。
信号与槽之间的连接可以是多对一的,也可以是一对多的。
信号与信号之间也可以建立连接。
信号
信号是公共访问函数,且必须没有返回值,可以从任何地方发出,但我们建议只从定义信号及其子类的类发出信号。
当发出信号时,连接到它的槽通常立即执行,就像普通的函数调用一样,当这种情况发生时,信号和槽机制完全独立于任何GUI
事件循环,一旦所有槽都返回,emit
语句之后的代码就会执行。
当使用队列连接时,情况略有不同,在这种情况下,emit关键字后面的代码将立即继续执行,而槽将稍后执行。
如果多个槽连接到一个信号,则在信号发出时,这些槽将按照它们连接的顺序依次执行。
信号是由moc
自动生成的,不能在.cpp
文件中实现。它们不能有返回类型(即必须使用void
)。
语法:
1 2
| signals: void 信号名称(参数);
|
例如:
1 2
| signals: void xxSig(int i, double d);
|
槽
当一个连接到槽的信号发出时,就会调用这个槽。
槽是普通的C++
函数,可以正常调用,与其他函数不同的是,信号可以与它们建立连接。
与回调相比,信号和槽稍微慢一些,原因在于需要定位连接对象、安全地遍历所有连接以及处理参数所需的开销。
注意,在与基于Qt
的应用程序一起编译时,定义称为信号或槽的变量的其他库可能会导致编译器警告和错误。要解决这个问题,需要使用#undef屏蔽掉有问题的预处理器符号。
所有包含信号或槽的类必须在声明的顶部提到Q_OBJECT
。这些类还**必须(直接或间接)**派生自QObject
。
槽函数需要有实现。
可以通过一个disconnect()
中断所有连接。如果使用了Qt::UniqueConnection
,则只有在不重复的情况下才会进行连接;如果已经有一个重复的信号(相同对象上相同槽的相同信号),连接将会失败,connect()
会返回false
。
语法:
1 2
| slots: [public|protected|private] void 槽名称(参数);
|
例如:
1 2
| public slots: void xxSigHandler(int i);
|
信号与槽的连接
使用connect()
函数进行连接:
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
| static QMetaObject::Connection connect( const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection );
static QMetaObject::Connection connect( const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection );
inline QMetaObject::Connection connect( const QObject *sender, const char *signal, const char *member, Qt::ConnectionType type = Qt::AutoConnection ) const;
template <typename Func1, typename Func2> static inline QMetaObject::Connection connect( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection );
template <typename Func1, typename Func2> static inline typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0, QMetaObject::Connection>::type connect( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot );
|
信号槽连接的几种方式
可以通过以下3种方式进行连接:
SIGNAL
和SLOT
宏(字符串形式)
- 函数指针
C++11
的lambda
表达式
SIGNAL
和SLOT
宏
这两个宏,会把传入的内容转换为字符串,它们的定义如下:
1 2
| #define SIGNAL(arg) #arg #define SLOT(arg) #arg
|
传入SIGNAL
宏的函数签名的参数个数不能比传入SLOT
宏的函数签名的参数个数少。
也就是说,信号的参数个数需要大于或等于槽的参数个数,且信号中的前N
个参数必须要与对应的槽参数的类型相同(N
为槽函数中参数的个数),否则在Qt Creator
中会出现:
QObject::connect: Incompatible sender/receiver arguments
传入SLOT
宏的可以是成员函数、静态函数、lambda
表达式。
1 2 3
| connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*))); connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed())); connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));
|
函数指针
此时,槽函数必须是一个普通成员函数或静态函数。
1
| connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
|
信号或槽有重载时,如果直接传入函数指针,则无法判断到底要使用哪个信号、哪个槽,此时,需要通过QOverload
或显式创建指针变量进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| signals: void brake(int force); void brake(int force, int a); public slots: void stopRunning(double force);
connect(&car, qOverload<int>(&Car::brake), &car, &Car::stopRunning); connect(&car, qOverload<int, int>(&Car::brake), &car, &Car::stopRunning);
void (Car::*ptr1)(int) = &Car::brake; void (Car::*ptr2)(int, int) = &Car::brake; connect(&car, ptr1, &car, &Car::stopRunning); connect(&car, ptr2, &car, &Car::stopRunning);
|
使用函数指针进行信号与槽的连接,有以下优势:
- 编译器可以检查信号参数与槽参数是否兼容(编译期检测)
- 如有必要,编译器会对参数进行转换(比如,double转int,int转double)
C++11
的lambda
表达式
1 2 3
| connect(sender, &QObject::destroyed, this, [=](){ this->m_objects.remove(sender); });
|
Qt::ConnectionType
序号 |
类型 |
描述 |
1 |
Qt::AutoConnection |
默认。如果接收者与发送的信号在同一个线程中,则使用Qt::DirectConnection;否则,则使用Qt::QueuedConnection。具体使用哪个,是在信号发送时才确定的。 |
2 |
Qt::DirectConnection |
信号发送时,槽函数立刻执行。槽函数在信号发送线程中执行。 |
3 |
Qt::QueuedConnection |
槽函数在控制权交还给接收线程的运行循环时执行。槽函数在接收线程中执行。 |
4 |
Qt::BlockingQueuedConnection |
与Qt::QueuedConnection一样,但信号线程会一直阻塞,直到槽函数返回,因此信号线程与接收线程不能是同一个,否则会发生死锁。 |
5 |
Qt::UniqueConnection |
可以与上述几种类型结合使用(使用按位或“|”),如果连接已经存在,那么新的连接将会失败。Qt 4.6引入。 |
6 |
Qt::SingleShotConnection |
可以与上述几种类型结合使用(使用按位或“|”),槽函数只会执行一次,当信号发送时,连接会自动断开。Qt 6.0引入。 |
在信号、槽中使用自定义类型的参数
如果调用connect
函数时,Qt::ConnectionType
设置的是Qt::QueuedConnection
,那么,自定义类型必须是Qt
元对象系统所知道的类型,否则在连接时会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #ifndef MYOBJECT_H #define MYOBJECT_H
#include <QObject> #include "MyCustomType.h"
class MyObject : public QObject { Q_OBJECT public: explicit MyObject(QObject *parent = nullptr);
private:
signals: void customTypeSig(MyCustomType val);
public slots: void handlerCustomType(MyCustomType val); };
#endif
|
注意,最后一个参数是Qt::QueuedConnection
:
1
| connect(&obj, &MyObject::customTypeSig, &obj, &MyObject::handlerCustomType, Qt::QueuedConnection);
|
错误信息:
1 2
| QObject::connect: Cannot queue arguments of type 'MyCustomType' (Make sure 'MyCustomType' is registered using qRegisterMetaType().)
|
需要在连接前,加入:
1
| Q_DECLARE_METATYPE(MyCustomType)
|