Qt信号与槽

参考资料:

  • 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种方式进行连接:

  1. SIGNALSLOT宏(字符串形式)
  2. 函数指针
  3. C++11lambda表达式

SIGNALSLOT

这两个宏,会把传入的内容转换为字符串,它们的定义如下:

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);

// 使用QOverload
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);

使用函数指针进行信号与槽的连接,有以下优势:

  1. 编译器可以检查信号参数与槽参数是否兼容(编译期检测)
  2. 如有必要,编译器会对参数进行转换(比如,double转int,int转double)

C++11lambda表达式

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 // MYOBJECT_H

注意,最后一个参数是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)

Qt信号与槽
https://daniate.github.io/2023/08/09/Qt信号与槽/
作者
Daniate
发布于
2023年8月9日
许可协议