「C++ Qt6 开发」信号与槽
1 信号与槽简介
1.1 信号与槽
信号与槽是 Qt 的核心机制。当某个事件发生时(如按钮点击),对象会发出一个信号;接收方通过槽函数处理该信号。
信号(signal)是在特定情况下被发射的通知,例如 QPushButton 比较常见的信号就是点击鼠标时的 clicked() 信号。
槽(slot)是对信号进行响应的函数。槽就是函数,所以也称为槽函数。槽函数与一般的函数不同的是,槽函数可以与信号相关联,当 信号被发射时,关联的槽函数被自动执行。
1.2 QObject::connect() 函数
信号与槽 的关联是用函数 QObject::connect() 实现的。connect() 函数的基本格式主要有两种:
-
Qt 4 传统风格(字符串匹配,不推荐)
1
connect(sender, SIGNAL(signalName(args)), receiver, SLOT(slotName(args)));
SIGNAL()
和SLOT()
宏 将函数签名转为字符串,例如SIGNAL(clicked(bool))
。如果信号和槽函数带有参数,需要参数列表完全匹配。
-
Qt 5/6 推荐风格(类型安全连接)
1
2
3connect(sender, &SenderClass::signalName,
receiver, &ReceiverClass::slotName,
connectionType = Qt::AutoConnection);-
sender
:发出信号的对象指针(如 button) -
&SenderClass::signalName
:指向信号成员函数的指针(如 &QPushButton::clicked) -
receiver
:接收信号的对象指针(如 this) -
&ReceiverClass::slotName
:指向槽成员函数的指针(如 &MainWindow::handleClick) -
connectionType
(可选):连接类型(默认为 Qt::AutoConnection)
Qt 5/6 推荐风格(类型安全连接)能够在编译时进行类型检查,而 Qt4 字符串匹配方式只会在运行时报错,连接失败不会编译报错。 Qt 5/6 推荐风格能够支持重载信号的选择,也避免字符串匹配错误。
-
一个信号可以连接多个槽函数;多个信号可以连接同一个槽函数;一个信号也可以连接到另一个信号上。
1.3 QObject::connect() 连接类型
类型 | 描述 |
---|---|
Qt::AutoConnection (默认) |
自动选择直接连接(同线程)或队列连接(跨线程) |
Qt::DirectConnection |
信号发出后立即调用槽(在发送者线程执行) |
Qt::QueuedConnection |
槽在接收者线程的事件循环中异步执行(跨线程安全) |
Qt::BlockingQueuedConnection |
同步执行槽,发送者线程阻塞直到槽完成(跨线程,需避免死锁) |
Qt::UniqueConnection |
确保同一信号槽不会重复连接(可与其他类型组合使用,如 `Qt::QueuedConnection |
-
Qt::AutoConnection
(默认)自动检测发送者和接收者是否在同一线程。
- 如果同线程 → 使用 DirectConnection(立即执行)
- 如果跨线程 → 使用 QueuedConnection(异步执行)
1
2
3connect(sender, &Sender::signal, receiver, &Receiver::slot);
// 等同于
connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::AutoConnection);当对象在不同线程间移动时,连接行为可能意外改变。
-
Qt::DirectConnection
信号发出后 立即在发送者线程中调用槽函数。
1
2
3connect(button, &QPushButton::clicked,
this, &MainWindow::handleClick,
Qt::DirectConnection);适用于 发送者和接收者一定在同一线程 的情形。如果发送者和接收者不在同一线程,会导致程序崩溃(数据竞争、死锁、随机崩溃等)。
若多个线程同时发射同一信号,槽函数会被多个线程同时调用。如果槽函数访问或修改共享数据(如全局变量、类成员变量),且没有同步机制(如互斥锁),就会发生数据竞争,最终可能出现错误的结果。
当信号发射线程和槽函数执行线程持有不同的锁,线程 A 持有锁 L1 , 发射信号并等待槽函数返回;而 线程 B 持有锁 L2 ,正在执行槽函数,但槽函数需要锁 L1 才能继续执行,此时线程 A 等待 B 执行完槽函数释放 L2,而 B 等待 A 释放 L1,双方永久阻塞,形成了死锁。
-
Qt::QueuedConnection
信号发出后,槽函数不会立即执行。将调用请求 放入接收者线程的事件队列,接收者线程处理事件时 异步执行槽函数。
这样保证线程安全,槽函数在接收者线程上下文执行,避免了数据竞争。
-
Qt::BlockingQueuedConnection
槽函数在接收者线程执行,与
Qt::QueuedConnection
的区别在于 发送者线程会阻塞直到槽函数执行完成,需要严格避免死锁。适用于 需要同步结果的跨线程调用 的情形,例如 从工作线程获取实时计算结果。
-
Qt::UniqueConnection
不是独立连接类型,需与其他类型组合使用。确保同一信号槽对不会重复连接。
1
2
3
4
5
6
7
8
9// 防止多次连接同一信号槽
connect(button, &QPushButton::clicked,
this, &MainWindow::processClick,
Qt::UniqueConnection); // 自动包含 Qt::AutoConnection
// 显式组合其他类型
connect(worker, &Worker::finished,
this, &MainWindow::cleanup,
Qt::QueuedConnection | Qt::UniqueConnection);
2 信号与槽的使用
2.1 信号与槽编辑器的使用(Qt Designer)
Qt 界面组件都是从 QWidget 类继承而来的,都支持信号与槽的功能。每个类都有一些内建的信号与槽函数。例如,QPushButton 类的常用信号是 clicked(),当按钮被点击时信号被发射。此处以一个 QDialog 项目为例,在 QDialog 中有以下几个公有的槽函数:
- accept(): 会关闭对话框,表示肯定的选择。
- reject(): 会关闭对话框,表示否定的选择。
- close(): 关闭对话框。
可以在 Qt Designer 中的 Signal and Slot 编辑器中设置组件的内建信号与其他组件的公有槽函数的关联:
2.2 Go to slot 生成槽函数
在 Qt Designer 中,可以选择要设置信号与槽函数关联的组件,右键找到 Go to slot 来生成槽函数原型。例如,选择一个复选框 chkBoxBold,点击 Go to slot,会出现如下对话框:
这个对话框显示了当前组件 所有可用的信号。
复选框 chkBoxBold 被勾选时会发出 clicked() 和 clicked(bool) 信号,其中带有 bool 参数的 clicked(bool) 以复选框的勾选状态作为参数,因此此处选择 clicked(bool) 信号。
之后 Dialog 类的 private slot 中会 自动生成槽函数的声明,函数名是根据发射信号的对象名和信号名自动命名的:
同时,在 dialog.cpp 中会自动生成槽函数的框架。我们可以在槽函数框架下添加代码,实现对文本框文字字体的控制:
1 |
|
在编译生成的 ui_dialog.h 文件中,有自动生成的如下连接语句:
1 |
|
其中前两句是通过 Signal and Slot 编辑器生成的构建关联的代码。
QMetaObject::connectSlotsByName(Dialog);
的功能是搜索 Dialog 界面中的所有组件,将名称匹配的信号与槽关联起来。例如有槽函数的名称为 on_chkBoxBold_clicked(bool)
,这正好是 chkBoxBold 的 clicked(bool) 信号的槽函数,connectSlotsByName
会将他们关联起来。相当于执行如下语句(字符串匹配):
1 |
|
2.3 使用自定义槽函数
- 在 dialog.h 类头文件中声明槽函数(需在 public slots 或 private slots 区域内)。
- 在 dialog.cpp 源文件中实现槽函数逻辑。
- 在 Dialog 类构造函数中使用 connect() 绑定信号与槽。
例如本示例中的字体颜色函数。在 private slots:
中声明颜色槽函数 void do_setFontColor();
。
在 dialog.cpp 源文件中实现 do_setFontColor() 的逻辑:
1 |
|
在 Dialog 类构造函数中构建信号与槽函数的关联:
1 |
|
完成后的效果如下图所示:
参考
《Qt 6 C++ 开发指南》