QT基础二、信号和槽
一、什么是信号和槽?
1、简述
在Qt框架中,信号和槽(Signals and Slots) 是一种用于对象间通信的机制。它是一种非常强大且灵活的设计模式,广泛应用于事件驱动编程中。信号和槽机制允许对象之间以松耦合的方式进行交互,而不需要直接调用彼此的方法。
2、核心概念
-
信号(Signal):
- 信号是当某个特定事件发生时发出的通知。例如,按钮被点击、滑块值改变等。
- 信号本身不执行任何操作,它只是通知其他对象发生了某些事情。
-
槽(Slot):
- 槽是一个普通的成员函数,可以像普通函数一样被调用。
- 槽的主要作用是响应信号,并执行相应的逻辑。
-
连接(Connection):
- 信号和槽通过
QObject::connect
函数建立连接。 - 当信号被触发时,与之连接的槽函数会被自动调用。
- 信号和槽通过
3、工作原理
信号和槽机制的核心思想是基于回调机制,但比传统的回调更安全和灵活。以下是其工作流程:
-
定义信号和槽:
- 在类中使用
signals
关键字声明信号。 - 使用
slots
关键字声明槽函数。
- 在类中使用
-
建立连接:
- 使用
QObject::connect
将信号与槽绑定在一起。
- 使用
-
触发信号:
- 当某个事件发生时,信号会被发射(emit)。
- 连接的槽函数会自动被调用。
4、既然信号和槽机制的核心思想是基于回调机制,为什么不直接使用 C/C++的回调函数,而去使用信号槽?
1
)信号与槽,是松耦合的,信号发送者,不需要去了解接收者的具体信息(有哪些接口等)
回调函数,是紧密耦合,直接调用目标对象的特定函数。
2
)信号槽,比回调函数,使用起来更灵活
5、信号和槽的特点
-
类型安全:
- Qt 的信号和槽机制是类型安全的。如果信号和槽的参数类型不匹配,编译器会报错。
-
松耦合:
- 信号和槽机制使得对象之间的依赖关系最小化。发送信号的对象不需要知道接收信号的对象是谁。
-
多对多连接:
- 一个信号可以连接到多个槽,多个信号也可以连接到同一个槽。
6、信号槽的缺点
1. 使用信号槽,比使用回调函数,运送速度慢:
信号与槽函数执行,可能是异步的(仅使用直连方式连接信号槽时,槽函数才会被同步
执行,执行完之后,发送信号(
emit)
语句之后的代码才会被执行。
2.使用回调函数,都是同步方式执行的。
二、信号和槽的关联(手动)
1、打开上一期的代码,在ui图搜索scroll(滚动条)
2、将垂直滚动条Vertical Scroll Bar拖动至合适位置
取一个name
Vertical Scroll Bar ---> scrollBar
3、mainwindow.cpp的代码:
// MainWindow 构造函数定义
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) // 调用父类构造函数,将 parent 传递给 QMainWindow 的构造函数
, ui(new Ui::MainWindow) // 初始化 ui 成员变量,创建一个 Ui::MainWindow 对象
{
ui->setupUi(this);
// 设置窗口标题为 "欢迎来到召唤师峡谷"
setWindowTitle("欢迎来到召唤师峡谷");
// 使用信号和槽机制连接滚动条 (QScrollBar) 和数字显示框 (QSpinBox 或 QLCDNumber)
// 当滚动条的值发生变化时,将新的值传递给数字显示框进行更新
connect(ui->scrollBar, // 滚动条对象
SIGNAL(valueChanged(int)), // 滚动条的 valueChanged 信号(当滚动条值变化时触发)
ui->age, // 数字显示框对象
SLOT(setNum(int))); // 数字显示框的 setNum 槽函数(用于设置显示的数字)
}
运行结果:
拖动滚动条,age会被改变
三、信号和槽的关联(自动)
1、添加一个label和line edit
label ---> textLabel
line edit ---> textEdit
2、右击文本框,点击转到槽,点击textChanged
3、在生成的函数写这段代码
// 定义一个槽函数,当 textEdit 的内容发生变化时触发
void MainWindow::on_textEdit_textChanged(const QString &arg1)
{
// 将 textEdit 的内容(通过参数 arg1 传递)设置为 textLabel 的显示文本
ui->textLabel->setText(arg1);
}
4、运行结果
textLabel的内容会随着textEdit的内容变化而变化
注:能自动关联就尽量自动关联,一般来说,自己写的类只能手动关联
四、自定义信号和槽
1、定义信号和槽的基本规则
-
信号(Signal):
- 使用
signals
关键字声明。 - 信号没有函数体,只需声明函数签名。
- 信号通过
emit
关键字触发。
- 使用
-
槽(Slot):
- 使用
slots
关键字声明(在 Qt 5 及以上版本中,普通成员函数也可以作为槽)。 - 槽函数可以有函数体,像普通成员函数一样实现逻辑。
- 使用
-
连接信号和槽:
- 使用
QObject::connect
函数将信号与槽绑定
- 使用
2、添加一个MyClass类
注:开发中不会让信号和槽出现在同一个类,以下都是演示效果
3、在MyClass.h写入一下代码
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QApplication>
#include <QObject>
#include <QDebug>
// 自定义类,继承自 QObject
class MyClass : public QObject {
Q_OBJECT // 必须包含此宏,用于启用元对象系统
public:
MyClass(QObject *parent = nullptr) : QObject(parent) {}
// 定义一个槽函数
void mySlot(const QString &message) {
qDebug() << "接收到消息:" << message;
}
signals:
// 定义一个信号
void mySignal(const QString &message);
public:
// 触发信号的函数
void doSomething() {
QString message = "Hello, World!";
emit mySignal(message); // 使用 emit 关键字发射信号
}
};
#endif // MYCLASS_H
4、在main.cpp添加一下代码
#include "mainwindow.h"
#include "myclass.h"
#include <QApplication>
void test() //测试用的函数
{
// 创建两个对象
MyClass sender; // 发送信号的对象
MyClass receiver; // 接收信号的对象
// 连接信号和槽
QObject::connect(&sender,
&MyClass::mySignal,
&receiver,
&MyClass::mySlot);
// 触发信号
sender.doSomething();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
test();
//MainWindow w;
//w.show();
return a.exec();
}
5、运行结果
五、信号和槽的连接方式
1、自动连接(默认方式)
- 特点:
- 如果信号和槽在同一线程中,使用直接连接(
Qt::DirectConnection
)。 - 如果信号和槽在不同线程中,使用队列连接(
Qt::QueuedConnection
)。
- 如果信号和槽在同一线程中,使用直接连接(
- 适用场景:
- 默认情况下,
QObject::connect
使用自动连接方式。
- 默认情况下,
QObject::connect(sender,
&Sender::mySignal,
receiver,
&Receiver::mySlot);
2、 直接连接(Qt::DirectConnection
)
- 特点:
- 槽函数在信号发出的线程中立即执行。
- 适用于同一线程中的对象通信。
- 适用场景:
- 当需要快速响应信号时。
QObject::connect(sender,
&Sender::mySignal,
receiver,
&Receiver::mySlot,
Qt::DirectConnection);
效果类似于“函数调用”,同步执行: 使用 emit 发送信号后,槽函数被直接调用,调用完成之后,再执行 emit
之后的语句。
3、队列连接(Qt::QueuedConnection
)
- 特点:
- 槽函数在接收者的线程中异步执行。
- 适用于跨线程通信。
- 适用场景:
- 当信号发出者和接收者位于不同的线程时。
QObject::connect(sender,
&Sender::mySignal,
receiver,
&Receiver::mySlot,
Qt::QueuedConnection);
效果类似于“异步函数调用”。当信号发出后,信号被添加到“信号队列”中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,再调用相应的槽函数。 emit 发送信号后,直接执行 emit
后面的代码,不需要等待槽函数执行完毕。
4、阻塞队列连接(Qt::BlockingQueuedConnection
)
- 特点:
- 类似于队列连接,但会阻塞信号发出的线程,直到槽函数执行完成。
- 仅适用于跨线程通信。
- 适用场景:
- 当需要确保槽函数执行完成后再继续信号发出者的逻辑时。
- 注意:
- 如果信号发出者和接收者在同一线程中使用此连接方式,会导致死锁。
QObject::connect(sender,
&Sender::mySignal,
receiver,
&Receiver::mySlot,
Qt::BlockingQueuedConnection);
与队列连接的基础上,加上阻塞发送信号所在的线程。 用于在不同线程之间进行对象之间的通信。 它可以确保发送者在发出信号后立即等待接收者处理完槽函数后才继续执行。 当信号被触发时,发送者会阻塞直到接收者处理完对应的槽函数,并且该槽函数会在接收者所属的线程中执行。这种连接类型适用于需要不同线程需要同步处理的情况。
5、独占连接(Qt::UniqueConnection
)
- 特点:
- 确保同一个信号和槽只能连接一次。
- 如果尝试重复连接,
QObject::connect
会返回false
。
- 适用场景:
- 当需要避免重复连接时。
bool success = QObject::connect(sender,
&Sender::mySignal,
receiver,
&Receiver::mySlot,
Qt::ConnectionType(Qt::AutoConnection|Qt::UniqueConnection));
if (!success) {
qDebug() << "连接失败:信号和槽已存在连接。";
}
不能单独使用,需要和其他类型组合使用, 用来确保指定的发送者、指定的信号、指定的接受者、指定的槽,只存在唯一的一种连接。
即,用来避免:
1.
避免信号和槽,以某种方式连接后,然后又调用
connect
以另一种方式连接
2.
避免建立多个重复的信号槽后,一个信号,将导致重复发送多个信号
注:一个信号可以关联多个槽,一个槽也可以关联多个信号