1. 什么是信号槽机制
信号槽
是Qt框架的核心特性之一,它提供了一种对象间通信的机制。与传统的回调函数相比,信号槽机制具有以下优势:
- 类型安全:信号和槽的参数类型由编译器检查
- 松耦合:发送者不知道也不关心接收者是谁
- 灵活性:一个信号可以连接多个槽,多个信号也可以连接同一个槽
在Qt中,信号(Signal)
是在特定事件发生时被发射的事件通知,而槽(Slot)
是对信号做出响应的成员函数。当信号被发射时,所有连接到该信号的槽函数都会被自动调用。
2. 信号和槽的关联
信号和槽通过QObject::connect()
函数建立连接,基本语法如下:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
在示例代码中,我们看到了两种连接方式:
// 方式1:新式语法(推荐)
connect(btn, &QPushButton::clicked, this, &MainWindow::mySlot);
// 方式2:旧式语法
connect(btn, SIGNAL(clicked()), this, SLOT(mySlot()));
关键点:
- 使用
SIGNAL()
和SLOT()
宏时,函数原型只能包含参数类型,不能有参数名 - 新式语法在编译时进行类型检查,更安全
- 连接成功后,当信号被发射时,槽函数会自动调用
3. 定义自己的信号
在Qt中,我们可以自定义信号。信号需要在类的signals:
区域声明,且不需要实现:
signals:
void mySignal(Student stu);
信号特点:
- 返回类型必须是
void
- 信号可以有参数,参数类型必须能被Qt的元对象系统识别
- 信号只需声明,不需实现
- 使用
emit
关键字发射信号
4. 定义自己的槽
槽是普通的成员函数,可以在public slots:
、protected slots:
或private slots:
区域声明:
public slots:
void mySlot();
void mySlot2(Student stu);
槽的特点:
- 可以是任何访问权限的成员函数
- 可以有返回值,但通常返回
void
- 可以有参数,参数类型必须与连接的信号兼容
- 需要实现函数体
5. 发送信号
使用emit
关键字发送信号:
void MainWindow::mySlot()
{
Student s;
s.age = 19;
s.score = 100;
emit mySignal(s); // 发射信号
}
6. 信号和槽的连接方式
Qt提供了多种连接类型,通过Qt::ConnectionType
指定:
连接类型 | 描述 |
---|---|
Qt::AutoConnection |
默认方式,自动决定连接类型 |
Qt::DirectConnection |
信号发射时立即调用槽,在发射线程执行 |
Qt::QueuedConnection |
槽在接收者线程的事件循环中调用 |
Qt::BlockingQueuedConnection |
类似QueuedConnection,但发送线程会阻塞 |
Qt::UniqueConnection |
防止重复连接相同的信号和槽 |
7. 信号和槽的对应关系
信号和槽的连接可以有以下几种形式:
- 一对一:一个信号连接一个槽
- 一对多:一个信号连接多个槽(按连接顺序执行)
- 多对一:多个信号连接同一个槽
- 信号连接信号:一个信号触发另一个信号
示例代码中展示了多种连接方式:
// 一个信号连接一个槽
connect(btn, SIGNAL(clicked()), this, SLOT(mySlot()));
// 信号连接信号
connect(this, SIGNAL(mySignal(Student)), this, SLOT(mySlot2(Student)));
8. 信号槽的断开
使用disconnect()
函数断开信号槽连接:
// 断开特定信号和槽
disconnect(btn, SIGNAL(clicked()), this, SLOT(mySlot()));
// 断开对象所有连接
disconnect(btn, 0, 0, 0);
btn->disconnect();
9. 信号槽传递自定义类型的参数
要传递自定义类型,必须使用qRegisterMetaType()
注册:
// 注册自定义类型
qRegisterMetaType<Student>("Student");
// 然后才能用于信号槽连接
connect(this, SIGNAL(mySignal(Student)), this, SLOT(mySlot2(Student)));
要求:
- 自定义类型必须提供公有的默认构造函数、拷贝构造函数和析构函数
- 对于非QObject派生类,还需要使用
Q_DECLARE_METATYPE
宏声明
10. 完整代码回顾
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QPushButton>
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
struct Student {
int age;
int score;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void mySlot();
void mySlot2(Student stu);
signals:
void mySignal(Student stu);
private:
Ui::MainWindow *ui;
QPushButton *btn;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建一个按钮
btn = new QPushButton(this);
// 设置按钮位置
btn -> setGeometry(100,50, // x,y坐标,原点是左上角
80,40); // 宽度 高度
btn -> setText("我的按钮");
// 把自定义的数据类型Student, 注册到元数据
qRegisterMetaType<Student>("Student");
//connect(btn,&QPushButton::clicked,this,&MainWindow::mySlot);
connect(btn, SIGNAL(clicked()),
this, SLOT(mySlot()));
connect(this, SIGNAL(mySignal(Student)),
this, SLOT(mySlot2(Student)));
// connect(btn, SIGNAL(clicked()),
// this, SLOT(mySlot()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::mySlot()
{
// qDebug() << "已经点击";
// static int count = 0;
// count++;
// if(count >= 4){
// disconnect(btn, SIGNAL(clicked()),
// this, SLOT(mySlot()));
//disconnect(btn, 0, 0, 0);
//}
// 传递自定义类类型Student
Student s;
s.age = 19;
s.score = 100;
emit mySignal(s);
}
void MainWindow::mySlot2(Student stu)
{
qDebug() << stu.age << stu.score;
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}