Qt学习之 - 信号槽

Qt 的信号槽机制是 Qt 框架的核心特性之一,它是一种用于对象间通信的机制。信号槽机制取代了传统的回调函数,提供了更灵活、更安全的方式来处理事件和对象之间的交互。

一、 信号槽机制的基本概念

语法:connect(sender, signal, receiver, slot);

  • sender:信号发送者
  • signal:信号
  • receiver:信号接收者
  • slot:接收对象在接收到信号之后所需要调用的函数(槽函数)

1. 信号(Signal)

  • 信号是 Qt 中对象发出的一种通知,用于表示某种事件的发生。
  • 信号是一个函数声明,但没有实现(类似于接口)。
  • 例如,按钮被点击时,QPushButton 会发出 clicked() 信号,也可以手动触发。

2. 槽(Slot)

  • 槽是一个普通的成员函数,用于响应信号。
  • 槽可以有参数,也可以没有参数。
  • 槽可以被直接调用,也可以通过信号触发。

3. 连接(Connect)

  • 使用 QObject::connect() 函数将信号与槽连接起来。

  • 当信号发出时,连接的槽函数会被自动调用。

  • 语法:

    QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
    

    或者使用 Qt5 的新语法:

    QObject::connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot);
    

一句话概括就是:的什么东西作用在哪里,比如:你用手打他的脸就是 QObject::connect(你,手,他,脸);


二、 声明与使用

示例代码:

#include <QApplication>
#include <QPushButton>
#include <QObject>
#include <QDebug>

class Example : public QObject {
    Q_OBJECT

public slots:
    void handleClick() {
        qDebug() << "Button clicked!";
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QPushButton button("Click Me");
    Example example;

    QObject::connect(&button, &QPushButton::clicked, &example, &Example::handleClick);

    button.show();
    return app.exec();
}

上面代码示例作用是点击按钮会触发Example 类中的handleClick方法,执行并打印 “Button clicked!”

Qt中的信号槽分为系统控件自带的和我们自定义的,一般都是使用系统控件自带的,如果我们想要看一个控件他自带的信号槽有哪些,该怎么办呢,可以通过以下几种方法查找:

1. 系统自带的信号槽


1.1 在代码中选中控件类名(如 QPushButton),按 F1 键即可打开帮助文档。

在这里插入图片描述

在这里插入图片描述


1.2 或者选中类直接点击去,找到Q_SLOTSQ_SIGNALS 两部分

在这里插入图片描述


1.3 使用 Qt Designer,将控件拖入设计器。右键控件,选择 “转到槽…” 来查看常见槽。

在这里插入图片描述

2. 自定义信号槽

2.1 自定义信号

自定义信号需要使用 signals 关键字声明。注意:信号只需声明,不需要实现。

头文件声明

class CustomWidget : public QWidget {
    Q_OBJECT

public:
    explicit CustomWidget(QWidget *parent = nullptr);

signals:
    void customSignal(int value);
};
2.2 自定义槽

槽函数是普通成员函数,需要用 slots 关键字声明。

头文件

class CustomWidget : public QWidget {
    Q_OBJECT

public slots:
    void handleCustomSignal(int value);
};

实现文件

#include <QDebug>

void CustomWidget::handleCustomSignal(int value) {
    qDebug() << "收到有值的自定义信号:" << value;
}
2.3 连接自定义信号与槽
CustomWidget *widget = new CustomWidget();
connect(widget, &CustomWidget::customSignal, widget, &CustomWidget::handleCustomSignal);

// 手动发出信号
emit widget->customSignal(42);

2.4 总结
特性系统信号槽自定义信号槽
声明不需额外定义signals 声明
连接方式直接使用自定义 connect()
触发方式内部事件触发手动 emit
常见用途UI 交互自定义模块通信

三、 信号槽机制的特点

1. 松耦合
  • 信号和槽之间不需要知道对方的具体实现,只需要知道接口。
  • 发送信号的对象和接收槽的对象可以是完全独立的。
2. 类型安全
  • Qt5 的新语法在编译时会检查信号和槽的参数类型是否匹配。
  • 如果类型不匹配,编译会报错。
3. 多对多支持
  • 一个信号可以连接多个槽。
  • 一个槽可以响应多个信号。
4. 跨线程支持
  • 信号槽机制支持跨线程通信。
  • 通过 Qt::QueuedConnection 连接方式,信号可以在不同线程之间传递。

四、 信号槽在不同界面之间的交互

在 Qt 中,不同界面(窗口或对话框)之间的交互可以通过信号槽机制实现。下面是一个典型的使用场景:

1. 主窗口与子窗口的交互
  • 主窗口打开子窗口,并通过信号槽与子窗口通信。
  • 子窗口完成任务后,通过信号通知主窗口。

mainwindow.h (主窗口头文件)

创建主窗口类的头文件,定义类和槽:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QString>
#include "subwindow.h"

class QPushButton;

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);

private slots:
    void openSubWindow();
    void handleData(const QString &data);

private:
    QPushButton *button;
};

#endif // MAINWINDOW_H

mainwindow.cpp (主窗口源文件)

主窗口类的实现,包含与子窗口的交互逻辑:

#include "mainwindow.h"
#include <QPushButton>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
    button = new QPushButton("Open SubWindow", this);
    setCentralWidget(button);
    connect(button, &QPushButton::clicked, this, &MainWindow::openSubWindow);
}

void MainWindow::openSubWindow() {
    SubWindow *subWindow = new SubWindow(this);
    connect(subWindow, &SubWindow::dataSent, this, &MainWindow::handleData);
    subWindow->exec();
}

void MainWindow::handleData(const QString &data) {
    qDebug() << "Received data:" << data;
}

subwindow.h (子窗口头文件)

子窗口类的头文件,包含信号和槽的定义:

#ifndef SUBWINDOW_H
#define SUBWINDOW_H

#include <QDialog>

class QPushButton;

class SubWindow : public QDialog {
    Q_OBJECT
public:
    explicit SubWindow(QWidget *parent = nullptr);

signals:
    void dataSent(const QString &data);

private slots:
    void sendData();

private:
    QPushButton *button;
};

#endif // SUBWINDOW_H

subwindow.cpp (子窗口源文件)

子窗口类的实现,包括信号的发射和按钮的动作:

#include "subwindow.h"
#include <QPushButton>

SubWindow::SubWindow(QWidget *parent) : QDialog(parent) {
    button = new QPushButton("Send Data", this);
    connect(button, &QPushButton::clicked, this, &SubWindow::sendData);
}

void SubWindow::sendData() {
    emit dataSent("Hello from SubWindow!");
    close();
}

main.cpp (主程序入口)

主程序入口,初始化Qt应用程序并显示主窗口:

#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

运行结果:

在这里插入图片描述

4.2 多个窗口之间的通信
  • 使用单例模式或全局对象来管理窗口之间的通信。
  • 通过信号槽将数据传递到目标窗口。

五、 信号槽的高级用法

在 Qt 中,Lambda 表达式是一种非常强大的工具,可以简化信号槽的连接代码,尤其是在槽函数逻辑简单时。Lambda 表达式允许你直接在 connect 函数中定义槽函数的行为,而不需要单独定义一个槽函数。


1. Lambda 表达式的基本语法

Lambda 表达式是 C++11 引入的特性,其基本语法如下:

[capture](parameters) -> return_type {
    // 函数体
}
  • capture:捕获列表,用于引入外部变量。
  • parameters:参数列表,与普通函数的参数列表类似。
  • return_type:返回类型(可以省略,编译器会自动推导)。
  • 函数体:Lambda 表达式的实现代码。

2. Lambda 表达式在信号槽中的使用

在 Qt 中,Lambda 表达式可以直接作为槽函数使用。例如:

QPushButton *button = new QPushButton("Click Me", this);
connect(button, &QPushButton::clicked, [=]() {
    qDebug() << "Button clicked using Lambda!";
});

在这个例子中:

  • [=] 表示以值捕获的方式引入外部变量。
  • () 表示 Lambda 表达式没有参数。
  • qDebug() << "Button clicked using Lambda!"; 是 Lambda 表达式的函数体。

3. 捕获列表的使用

捕获列表用于控制 Lambda 表达式中如何访问外部变量。以下是常见的捕获方式:

(1)值捕获 ([=])
  • 以值的方式捕获所有外部变量。
  • 示例:
    int x = 10;
    connect(button, &QPushButton::clicked, [=]() {
        qDebug() << "Value of x:" << x;  // x 的值是 10
    });
    
(2)引用捕获 ([&])
  • 以引用的方式捕获所有外部变量。
  • 示例:
    int x = 10;
    connect(button, &QPushButton::clicked, [&]() {
        x = 20;  // 修改外部变量 x
        qDebug() << "Value of x:" << x;  // x 的值是 20
    });
    
(3)混合捕获 ([=, &x][&, x])
  • 可以混合使用值和引用捕获。
  • 示例:
    int x = 10;
    int y = 20;
    connect(button, &QPushButton::clicked, [=, &x]() {
        x = 30;  // 修改 x(引用捕获)
        qDebug() << "x:" << x << ", y:" << y;  // y 的值是 20(值捕获)
    });
    
(4)捕获特定变量 ([x][&x])
  • 只捕获特定的变量。
  • 示例:
    int x = 10;
    int y = 20;
    connect(button, &QPushButton::clicked, [x, &y]() {
        y = 30;  // 修改 y(引用捕获)
        qDebug() << "x:" << x << ", y:" << y;  // x 的值是 10(值捕获)
    });
    

4. Lambda 表达式的参数

Lambda 表达式可以接受参数,参数的类型和数量需要与信号的参数匹配。例如:

QSlider *slider = new QSlider(Qt::Horizontal, this);
connect(slider, &QSlider::valueChanged, [=](int value) {
    qDebug() << "Slider value:" << value;
});

在这个例子中:

  • valueChanged(int) 信号带有一个 int 类型的参数。
  • Lambda 表达式的参数列表 (int value) 与信号匹配。

5. Lambda 表达式的返回值

Lambda 表达式可以指定返回类型,但通常在与信号槽连接时不需要返回值。如果需要返回值,可以使用 -> return_type 语法。例如:

auto lambda = [](int a, int b) -> int {
    return a + b;
};
int result = lambda(10, 20);  // result = 30

6. Lambda 表达式的实际应用

(1)简化槽函数

如果槽函数逻辑简单,可以直接用 Lambda 表达式替代。例如:

connect(button, &QPushButton::clicked, [=]() {
    qDebug() << "Button clicked!";
});
(2)访问局部变量

Lambda 表达式可以方便地访问局部变量。例如:

int count = 0;
connect(button, &QPushButton::clicked, [=]() mutable {
    count++;
    qDebug() << "Button clicked" << count << "times";
});

注意:

  • 如果需要在 Lambda 表达式中修改值捕获的变量,需要使用 mutable 关键字。
(3)跨线程通信

Lambda 表达式可以与 Qt::QueuedConnection 结合,实现跨线程通信。例如:

QThread *thread = new QThread;
Worker *worker = new Worker;
worker->moveToThread(thread);

connect(worker, &Worker::resultReady, this, [=](const QString &result) {
    qDebug() << "Result:" << result;
}, Qt::QueuedConnection);

thread->start();

六、 信号槽的注意事项

  • 内存管理:确保连接的对象在信号发出时仍然有效,避免悬空指针。
  • 性能:频繁的信号槽调用可能会影响性能,尤其是在主线程中。
  • 循环连接:避免信号槽之间的循环连接,否则会导致无限递归。

七、 总结

Qt 的信号槽机制是一种强大且灵活的对象间通信方式,适用于处理事件、界面交互、跨线程通信等场景。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值