目录
[二、connect 函数使用](#二、connect 函数使用)
[1. QT 类继承关系](#1. QT 类继承关系)
[2. connect 构成](#2. connect 构成)
[3. 如何查找信号与槽?](#3. 如何查找信号与槽?)
[1. 代码中定义](#1. 代码中定义)
[2. 图形化界面定义](#2. 图形化界面定义)
[1. 声明信号](#1. 声明信号)
[2. 发射信号](#2. 发射信号)
[3. 图形化界面创建(同上)](#3. 图形化界面创建(同上))
[六、Q_OBJECT 宏](#六、Q_OBJECT 宏)
[七、QT 为什么使用信号和槽机制?](#七、QT 为什么使用信号和槽机制?)
[八、Lambda 表达式在槽函数中的应用](#八、Lambda 表达式在槽函数中的应用)
一、信号与槽概念
-
信号源:由哪个控件发出信号。
-
信号类型:用户的不同操作,如点击按钮、输入光标变化等,都能触发不同类型的信号。
-
信号处理方式:即为槽函数(slot),本质上是一种回调函数。
-
QT 使用
connect函数将一个信号(signal)和一个槽(slot)关联起来。
二、connect 函数使用
1. QT 类继承关系
-
大致的继承关系是:
QObject->QWidget->QPushButton,QLineEdit...... -
QObject是 QT 所有内置类的"老祖宗"。
2. connect 构成
-
构成要素:由哪个控件发出信号,发出什么类型的信号,由哪个对象负责处理,以及如何处理。
-
示例代码:
cppQPushButton* p1 = new QPushButton(this); p1->setText("关闭"); p1->move(200, 300); connect(p1, &QPushButton::clicked, this, &Widget::close); -
注意点:
-
connect的第一个参数应为QWidget类型(或其父类),而QPushButton是它的子类,因此子类对象传给父类引用是允许的(向上转型)。 -
click和clicked的区别:click是"点击"动作,是一个槽函数;clicked是"检测到点击",是一个信号函数。
-
槽函数和信号函数在 QT Creator 左侧的"对象浏览器"中图标不同。
-
-
connect要求前两个参数的类型匹配,即第二个参数(信号)必须是第一个参数(发送者对象)类型或其基类中定义的信号。 -
close是QWidget类中内置的槽函数(用于关闭窗口),而我们的Widget类继承了QWidget,所以可以直接使用。
3. 如何查找信号与槽?
问题 :怎么知道 QPushButton 有 clicked 这个信号,以及 QWidget 有 close 这个槽?
-
查阅官方文档:
-
在
QPushButton Class的文档里,直接列出的信号函数可能没有clicked。 -
这时需要去查找它的父类。在父类
QAbstractButton Class的文档中,就能找到对应的clicked信号。
-
因为 QT 有不同类型的按钮(如
QPushButton,QRadioButton等),它们共有的信号就被抽象到共同的父类QAbstractButton中。
-
三、自定义槽函数
1. 代码中定义
-
在
widget.h的Widget类中声明槽函数:cppprivate slots: void handle(); -
在
widget.cpp中实现:cppvoid Widget::handle() { this->setWindowTitle("按钮点击"); } -
在构造函数或其它地方连接信号:
cppQPushButton* p1 = new QPushButton(this); connect(p1, &QPushButton::clicked, this, &Widget::handle);
2. 图形化界面定义
-
在
widget.ui的图形编辑器界面中,右键点击按钮控件,选择"转到槽..."。
-
选择对应的信号(如
clicked()),QT Creator 会自动在widget.h和widget.cpp中生成对应格式的槽函数。cpp// widget.h private slots: void on_pushButton_clicked(); // widget.cpp void Widget::on_pushButton_clicked() { this->setWindowTitle("点击按钮"); } -
命名规则 :
on_ + 控件对象名 + _ + 信号名。 -
之后,无需手动编写
connect语句即可生效。 -
原理 :在
ui_widget.h文件中,QT 使用connectSlotsByName函数自动将符合此命名规则的槽函数与对应控件的信号连接起来。 -
这种方法非常适用于图形化界面编辑,因为手动创建的槽函数并没有这个自动连接的特性。
四、自定义信号
-
相比于槽函数,由于用户在图形界面上的操作是可以穷举的,因此 QT 自带的信号通常就够用了。
-
自定义信号只需要声明即可,编译器会自动生成其实现代码。
1. 声明信号
-
在
widget.h的Widget类中:cppsignals: void mySignal();- 加上
signals关键字。
- 加上
2. 发射信号
-
使用
emit关键字来发射信号。cpp// 先连接信号与槽 connect(this, &Widget::mySignal, this, &Widget::changeTitle); // 在某个地方发射信号 emit mySignal(); -
注意 :信号的处理逻辑是在连接的槽函数中,因此没有
emit发射信号,对应的槽函数就不会被调用。
3. 图形化界面创建(同上)
- 同样可以在
.ui文件中右键控件"转到槽"来关联自定义信号(需要先在代码中定义好)。
五、信号和槽的参数
-
信号函数和槽函数都可以带有参数。
-
参数的类型必须匹配,但参数个数可以是:信号的参数个数 >= 槽的参数个数。
- 多余的参数会被忽略。
-
这种机制可以让信号与槽的关联更加灵活,确保每个参数都有对应值。
-
发射信号时,信号函数传递的参数会传递给槽函数,这有助于代码复用。
示例:
cpp
// widget.h
signals:
void mySignal(const QString& text);
private slots:
void mySlot(const QString& text);
// widget.cpp
void Widget::mySlot(const QString& text) {
this->setWindowTitle(text);
}
void Widget::on_pushButton_clicked() { // 按钮1
emit mySignal("按钮1按下");
}
void Widget::on_pushButton_2_clicked() { // 按钮2
emit mySignal("按钮2按下");
}
- 这样,点击两个不同的按钮,都可以通过发射同一个信号并传递不同的参数,来改变窗口的标题。
六、Q_OBJECT 宏
-
一个类如果希望使用信号和槽机制(定义信号或槽),就必须在类声明的开头加上
Q_OBJECT宏。 -
这个宏会由 MOC(元对象编译器)展开,生成支持信号槽机制和元对象系统所必需的代码。
七、QT 为什么使用信号和槽机制?
-
其他界面设计框架(如 MFC, WinForms)通常使用回调函数 ,直接将事件和处理函数绑定,不需要单独的
connect函数。 -
QT 的信号和槽机制可以实现多对多的连接(一个信号可以连接多个槽,一个槽也可以被多个信号连接),就像做"连线题"一样灵活。
- 但在绝大多数情况下,一对一的连接已经足够,因此这个特性有时不算是 QT 的绝对亮点。
-
核心优势:解耦合。
-
信号发送者不知道也不关心谁接收信号;槽接收者也不知道信号来自哪里。它们只通过
connect函数建立关联。 -
这种松耦合的设计可以减少组件之间的依赖,一个模块出问题,对其他模块的影响较小。
-
八、Lambda 表达式在槽函数中的应用
-
可以使用 Lambda 表达式作为槽函数,使代码更简洁。
cppQPushButton* q1 = new QPushButton(this); connect(q1, &QPushButton::clicked, this, [q1]() { q1->move(200, 300); }); -
这里需要使用捕获列表
[q1]来捕获按钮指针q1。 -
也可以使用
[=]按值捕获所有外部变量(包括q1)。- 由于 QT 中控件通常是动态创建的指针,使用
[=]捕获是安全的。相比于[&](按引用捕获),[=]不用过多考虑对象树的生命周期问题,因为捕获的是指针的副本,而指针指向的对象由 QT 对象树管理。
- 由于 QT 中控件通常是动态创建的指针,使用