小码哥-音视频学习笔记(第四天): QT开发基础-信号与槽

722 阅读4分钟

一、基本使用

  • 信号(Signal):比如点击按钮就会发出一个点击信号
  • 槽(Slot):一般也叫槽函数,是用来处理信号的函数
  • 官方文档参考:Signals & Slots

  • 上图中的效果是:
    • Object1发出信号signal1,交给Object2的槽slot1、slot2去处理
      • Object1是信号的发送者,Object2是信号的接收者
    • Object1发出信号signal2,交给Object4的槽slot1去处理
      • Object1是信号的发送者,Object4是信号的接收者
    • Object3发出信号signal1,交给Object4的槽slot3去处理
      • Object3是信号的发送者,Object4是信号的接收者
    • 1个信号可以由多个槽进行处理,1个槽可以处理多个信号
  • 通过connect函数可以将信号的发送者信号信号的接收者连接在一起。
connect(信号的发送者, 信号, 信号的接收者, 槽);

1、使用案例

  • 在主窗口添加一个关闭按钮
#include "mainwindow.h"
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QPushButton *btn = new QPushButton;
    btn->setText("关闭");
    btn->setFixedSize(100, 50);
    btn->setParent(this);

    this->setFixedSize(500, 500);
}
  • 运行后如下图

image.png

  • 使用 command + 鼠标左键 点击 QPushButton, 可以查看QPushButton的头文件

image.png

  • QPushButton的头文件中, 并没有找到信号的定义
  • 继续查看父类QAbstractButton的头文件

image.png

  • QAbstractButton的头文件中, 往下拉, 可以看到定义了四个信号(Q_SIGNALS)

image.png

  • 使用同样的办法, 可以在QMainWindow的父类QWidget中找到槽函数(Q_SLOTS)

image.png

  • 通过connect函数, 链接QPushButton信号QMainWindow槽函数
#include "mainwindow.h"

#include <QDebug>

#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QPushButton *btn = new QPushButton;
    btn->setText("关闭");
    btn->setFixedSize(100, 50);
    btn->setParent(this);

    this->setFixedSize(500, 500);

    connect(btn, &QPushButton::clicked, this, &MainWindow::close);
}

MainWindow::~MainWindow()
{
    qDebug("主窗口被关闭了");
}
  • 再次运行程序

image.png

  • 点击关闭按钮, 可以看到主窗口被关闭, 同时有打印输出

image.png

  • 可以通过disconnect函数断开信号与槽的链接
// 可以通过disconnect断开连接
disconnect(btn, &QPushButton::clicked, this, &MainWindow::close);

二、自定义信号与槽

  • 信号的发送者和接收者都必须继承自QObject,Qt中的控件最终都是继承自QObject,比如QMainWindow、QPushButton等。

1、信号的发送者

  • 新建文件sender, 定义Sender类作为信号的发送者

image.png

image.png

image.png

image.png

  • 查看生成的文件

image.png

  • sender.h
    • Q_OBJECT用以支持自定义信号和槽
    • 自定义的信号需要写在signals:下面
    • 自定义的信号只需要声明,不需要实现
#ifndef SENDER_H
#define SENDER_H

#include <QObject>

class Sender : public QObject
{
    Q_OBJECT
public:
    explicit Sender(QObject *parent = nullptr);
    
signals:
    // 定义信号, 只需要添加声明, 不需要添加实现
    void exit();
};

#endif // SENDER_H

image.png

  • sender.cpp
#include "sender.h"

Sender::Sender(QObject *parent) : QObject(parent)
{

}

image.png

2、信号的接收者

  • 创建receiver, 定义Receiver类, 作为信号的接收者, 与sender文件创建方法一致

image.png

  • receiver.h
    • 自定义的槽建议写在public slots:下面
#ifndef RECEIVER_H
#define RECEIVER_H

#include <QObject>

class Receiver : public QObject
{
    Q_OBJECT
public:
    explicit Receiver(QObject *parent = nullptr);
    
// 自定义槽
public slots:
    void handleExit();
};

#endif // RECEIVER_H

image.png

  • receiver.cpp
    • 自定义的槽函数, 必须写出实现
#include "receiver.h"
#include <QDebug>

Receiver::Receiver(QObject *parent) : QObject(parent)
{

}

// 实现槽函数,编写处理信号的代码
void Receiver::handleExit()
{
    qDebug() << "Receiver::handleExit()";
}

image.png

3、连接

  • 使用connect函数连接senderreceiver
  • mainwindow.cpp
#include "mainwindow.h"
#include "sender.h"
#include "receiver.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    Sender *sender = new Sender;
    Receiver *receiver = new Receiver;

    connect(sender, &Sender::exit, receiver, &Receiver::handleExit);

    // sender发送信号
    emit sender->exit();
}

MainWindow::~MainWindow()
{
}

image.png

  • 运行, 查看打印

image.png

  • 如果不在使用sender和receiver, 记得要销毁对象
#include "mainwindow.h"
#include "sender.h"
#include "receiver.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    Sender *sender = new Sender;
    Receiver *receiver = new Receiver;

    connect(sender, &Sender::exit, receiver, &Receiver::handleExit);

    // sender发送信号
    emit sender->exit();
    
    // 如果不在使用sender和receiver, 记得要销毁对象
    delete sender;
    delete receiver;
}

image.png

4、参数和返回值

  • 信号与槽都可以有参数和返回值:
    • 发信号时的参数会传递给槽
    • 槽的返回值会返回到发信号的位置
// 自定义信号
signals:
    int exit(int a, int b);
 
// 自定义槽
public slots:
    int handleExit(int a, int b);
 
int Receiver::handleExit(int a, int b)
{
    // Receiver::handleExit() 10 20
    qDebug() << "Receiver::handleExit()" << a << b;
    return a + b;
}
 
// 发出信号
int a = emit sender->exit(10, 20);
// 30
qDebug() << a;

image.png

image.png

image.png

image.png

  • 需要注意的是:信号的参数个数必须大于等于槽的参数个数。比如下面的写法是错误的:
// 自定义信号
signals:
    void exit(int a);
 
// 自定义槽
public slots:
    void handleExit(int a, int b);
  • 连接2个信号
    • 比如下图,连接了Object 1的Signal 1A和Object 2的Signal 2A
    • 当Object 1发出Signal 1A时,会触发Slot X、Slot Y
    • 当Object 2发出Signal 2A时,只会触发Slot Y,而不会触发Slot X

image.png

  • 创建sender2文件, 定义Sender2类, 作为第二个信号的发送者

image.png

  • 添加信号
#ifndef SENDER2_H
#define SENDER2_H

#include <QObject>

class Sender2 : public QObject
{
    Q_OBJECT
public:
    explicit Sender2(QObject *parent = nullptr);

signals:
    // 定义信号, 只需要添加声明, 不需要添加实现
    int exit2(int a, int b);
};

#endif // SENDER2_H

image.png

  • receiver.h中添加新的槽函数
#ifndef RECEIVER_H
#define RECEIVER_H

#include <QObject>

class Receiver : public QObject
{
    Q_OBJECT
public:
    explicit Receiver(QObject *parent = nullptr);

// 自定义槽
public slots:
    int handleExit(int num1, int num2);
    int handleExit2(int num1, int num2);
};

#endif // RECEIVER_H

image.png

  • receiver.cpp中添加槽函数的实现
#include "receiver.h"
#include <QDebug>

Receiver::Receiver(QObject *parent) : QObject(parent)
{

}

// 实现槽函数,编写处理信号的代码
int Receiver::handleExit(int num1, int num2)
{
    qDebug() << "Receiver::handleExit()" << num1 << num2;
    return num1 + num2;
}

int Receiver::handleExit2(int num1, int num2)
{
    qDebug() << "Receiver::handleExit2()" << num1 << num2;
    return num1 - num2;
}

image.png

  • mainwindow.cpp 中, 连接sendersender2的信号
#include "mainwindow.h"
#include "sender.h"
#include "sender2.h"
#include "receiver.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    Sender *sender = new Sender;
    Sender2 *sender2 = new Sender2;
    Receiver *receiver = new Receiver;

    // 连接 sender 和 sender2
    connect(sender, &Sender::exit, sender2, &Sender2::exit2);
    // 连接 sender 和 receiver
    connect(sender, &Sender::exit, receiver, &Receiver::handleExit);
    // 连接 sender2 和 receiver
    connect(sender2, &Sender2::exit2, receiver, &Receiver::handleExit2);

    // sender发送信号
    int res = emit sender->exit(100, 200);

    qDebug() << res;

    // 如果不在使用sender、sender2和receiver, 记得要销毁对象
    delete sender;
    delete sender2;
    delete receiver;
}
  • sender发送信号

image.png

5、Lambda

  • Lambda是一个函数代码块, 可以用于事件处理
connect(sender, &Sender::exit, [](int a, int b) {
    qDebug() << "lambda handle exit";
    return a + b;
});
#include "mainwindow.h"
#include "sender.h"
#include "sender2.h"
#include "receiver.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    Sender *sender = new Sender;
    connect(sender, &Sender::exit, [](int a, int b) {
        qDebug() << "lambda handle exit";
        return a + b;
    });

    int res = emit sender->exit(10, 20);
    qDebug() << res;
}

image.png

三、UI中的信号与槽

1、UI绑定槽

  • 新建项目, 勾选UI

image.png

  • 项目中生成mainwindow.ui文件

image.png

  • 双击mainwindow.ui文件

image.png

  • 添加两个按钮, 分别标为登录注册

image.png

  • 鼠标右键点击登录按钮

image.png

  • 选择转到槽

image.png

  • 选择clicked, 可以在mainwindow.hmainwindow.cpp文件中自动生成槽函数

image.png

image.png

  • 槽函数中添加打印代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_pushButton_clicked()
{
    qDebug() << "登录";
}
  • 执行程序, 点击登录按钮, 可以看到控制台有打印输出

image.png

2、UI绑定槽的命名规则

  • UI绑定槽的命名规则: on_控件的变量名_事件名
  • 上面的登录按钮, 在UI文件中名称是pushButton, 选择的事件是clicked

image.png

  • 修改注册按钮在UI中的名称为registerButton

image.png

  • mainwindow.h文件中, 手动添加registerButton触发的槽函数声明

image.png

  • mainwindow.cpp文件中, 手动添加registerButton触发的槽函数实现

image.png

  • 执行程序, 点击注册按钮, 可以看到控制台的打印输出

image.png

于是得知:ui文件中的控件会自动跟符合命名规则槽函数建立连接。

3、通过代码获取UI文件中的控件

  • ui文件中的控件可以在代码中通过ui->变量名访问。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 通过ui->访问ui文件中的2个按钮
    ui->pushButton->setFixedSize(200, 50);
    ui->registerButton->setFixedSize(200, 50);
}

image.png