一、信号与槽的概念
Qt的信号与槽(Signals and Slots)是一种用于对象间通信和事件处理的机制。它是Qt框架的核心特性之一,提供了一种灵活而强大的方式来连接对象之间的交互和响应。
在Qt中,一个信号(Signal)是一个特殊的成员函数,用于表示某个事件的发生。一个槽(Slot)是另一个成员函数,用于处理信号所代表的事件。信号可以被发射(emitted),而槽函数可以被连接(connected)到一个或多个信号上。
使用信号和槽的步骤如下:
- 定义信号:在类中声明一个信号,使用
signals
关键字,可以带有参数。 - 定义槽函数:在类中声明一个槽函数,使用
slots
关键字,与信号的参数列表一致。 - 连接信号和槽:使用
connect
函数将信号与槽函数连接起来。连接可以在运行时进行,也可以在代码中静态地指定。 - 发射信号:在适当的时机,使用
emit
关键字发射信号。一旦信号被发射,所有连接的槽函数将会被调用。
二、槽函数
本小节主要介绍Qt5以后的两种槽函数的写法,=普通槽函数和自动槽函数的写法。
在Qt中,槽函数的写法要求如下:
- 槽函数必须位于类的声明中,并且需要在类的声明中使用
slots
关键字进行声明。 - 槽函数可以具有参数,可以根据需要在槽函数的声明和定义中定义参数类型和数量。
- 槽函数的返回类型必须为
void
,因为槽函数不允许有返回值。
1. 普通槽函数
-
使用
slots
宏:在类的声明部分,使用slots
宏标记槽函数。cppclass MyObject : public QObject { Q_OBJECT public slots: void mySlot(int value, QString text); };
-
槽函数的实现:槽函数的实现与以前的版本相同。在类的实现部分,按照声明中的函数签名来实现槽函数。
cppvoid MyObject::mySlot(int value, QString text) { // 在槽函数中执行操作 qDebug() << "Received value:" << value; qDebug() << "Received text:" << text; }
2. 自动槽函数
在Qt5中,有一种自动匹配槽函数的命名格式,称为自动槽(Auto Slots)。自动槽使用一定的命名规则,使得当信号触发时,会自动调用对应的槽函数,无需手动连接信号和槽。
自动槽的命名规则如下:
- 槽函数的名称以
on
开头,后面跟着发射信号的对象名称和信号名,使用下划线连接。 - 槽函数的参数列表与信号的参数列表一致。
假设有一个名为btn
的按钮对象,根据命名规则,这个函数会被视为一个槽函数,并且会在名为btn
的对象发出clicked()
信号时自动调用,而不需要通过connect
函数连接。
cpp
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr);
private slots:
void on_btn_clicked();
};
三、信号
在Qt中,自定义信号的格式要求如下:
- 自定义信号必须在类的声明中使用
signals
关键字进行声明。 - 自定义信号可以具有参数,您可以根据需要在信号声明中定义参数类型和数量。
- 自定义信号的返回类型必须为
void
,因为信号只是一种通知机制,不返回任何值。
以下是一个示例,展示了一个带有参数的自定义信号的声明:
cpp
class MyObject : public QObject
{
Q_OBJECT
signals:
void myCustomSignal(int value, QString text);
};
当想要发出自定义信号时,可以使用emit
关键字,如下所示:
cpp
void MyObject::someFunction()
{
int value = 42;
QString text = "Hello";
emit myCustomSignal(value, text);
}
在上述示例中,someFunction
函数发出了myCustomSignal
信号,并传递了一个int
类型的值和一个QString
类型的文本。其他对象可以通过连接自定义信号与槽函数来响应自定义信号的发出,并在信号触发时执行相应的操作。
需要注意的是,为了使用自定义信号,类必须继承自QObject
,并且在类的声明中包含Q_OBJECT
宏以启用元对象系统的功能。
1. 跨UI发送信号
假设现在有一个Widget,点击按钮打开一个Dialog,通过点击Dialog中的按钮向Widget中发送信息并展示。
我们的设计逻辑应该是:
-
Dialog::on_btn_clicked()
:这是一个槽函数,负责处理按钮的点击事件。当按钮被点击时,它将发送信号。cppvoid Dialog::on_btn_clicked() { Data data{"Tom", 333, 18, true}; emit(sig_btn_click(data)); }
-
Dialog::signal_btn_clicked(Data data)
:这是一个自定义信号,它将与按钮的点击事件相关联。信号可以带有参数,这里的参数是一个名为data
的自定义数据对象。cppvoid sig_btn_click(Data data);
-
Widget::slot_dialog_btn_clicked(Data data)
:这是一个槽函数,用于接收来自Dialog
的信号并处理传递的数据。这个槽函数在QWidget
中定义,当signal_btn_clicked
信号被触发时,它将被调用。(这里借助lambda函数实现) -
Widget::on_btn_dialog_clicked()
:这是一个槽函数,用于接受QWidget
的按钮信号。生成Dialog
窗口,并通过connect
函数连接跨UI的信号与槽。cppvoid Widget::on_btn_dialog_clicked() { Dialog dialog; // slot_dialog_btn_clicked(Data data) connect(&dialog, &Dialog::sig_btn_click, this, [=](Data data){ QString message = "name: " + QString::fromStdString(data.name) + \ " id: " + QString::number(data.id) + \ " age: " + QString::number(data.age) + \ " gender: " + QString{data.gender ? "male" : "female"}; ui->lineEdit_cout->setText(message); }); dialog.exec(); }
2. 跨线程发送信号
(Todo:待完善)
子线程无法修改UI,必须通过发送信号的方式进行修改。但是Lambda可以直接修改。
四、connect 函数
connect
方法是Qt框架中用于建立信号与槽之间连接的函数。它允许您将一个对象的信号与另一个对象的槽函数关联起来,以便在信号触发时调用槽函数。
connect
方法的语法如下:
cpp
bool QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *slot,
Qt::ConnectionType type = Qt::AutoConnection);
sender
:发送信号的对象。signal
:信号的名称,以字符串形式表示。信号是在发送对象中声明的。receiver
:接收信号的对象。slot
:槽函数的名称,以字符串形式表示。槽函数是在接收对象中声明的。type
:连接类型,用于指定连接的行为。它可以是以下值之一:Qt::AutoConnection
:根据上下文自动选择连接类型。- 如果信号发送者和接收者位于同一线程中,则使用
DirectConnection
。这意味着信号触发时,槽函数会立即在同一线程中执行。 - 如果信号发送者和接收者位于不同线程中,则使用
QueuedConnection
。这意味着信号会被放入接收者对象所在线程的事件队列中,在事件循环处理时调用槽函数。(需要确保槽在接受方的线程中执行。) - 需要注意的是,当使用
AutoConnection
时,如果信号发送者和接收者位于不同线程,并且没有开启事件循环,那么连接将会失败。因此,在多线程的情况下,确保在接收者线程中有事件循环是非常重要的。
- 如果信号发送者和接收者位于同一线程中,则使用
Qt::DirectConnection
:直接连接,即在信号发出时立即调用槽函数。Qt::QueuedConnection
:队列连接,即将信号放入接收对象的事件队列中,在事件循环处理时调用槽函数。- 当控制权回到接受者所依附线程的事件循环时,槽函数被调用。
- 槽函数在接收者所依附线程执行。
Qt::BlockingQueuedConnection
:阻塞队列连接,与Qt::QueuedConnection
类似,但在槽函数返回之前阻塞发送对象的执行。Qt::UniqueConnection
:唯一连接,确保重复连接不会创建。
connect
方法的返回值是一个布尔值,表示连接是否成功建立。
五、重载信号、槽的 connect 方式
使用QOverload
,它的作用是返回重载函数的指针,模板参数为函数类型列表。
cpp
// 基于方法地址匹配 connect, 使用 QOverload 实现
connect(comboBox, QOverload<const QString&>::of(&QComboBox::currentIndexChanged),
label, &QLabel::setText);
connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
label, &QLabel::setText);