Qt操作指南:信号与槽机制

1. 信号和槽概述

  • 信号三个要素
    • 信号源:由哪个控件发出的信号
    • 信号的类型:用户进行不同的操作,就可能触发不同的信号
    • 信号的处理方式:槽(slot)=》函数
      • Qt中可以使用connect这样的函数,把一个信号和槽关联起来;后续只要信号出发了,Qt就会自动地执行槽信号
  • 一定是先把信号的处理方式准备好,再触发信号

2. connect

  • 和Linux TCP socket中建立连接的函数,没有任何关系,只是名字恰巧一样,是 QObject 提供的静态的成员函数
cpp 复制代码
connect(const QObject *sender,const char *signal,const QObject *receiver,const char *method,Qt::ConnectionType type = Qt::AutoConnection);
  • 参数列表:
    • sender:描述了当前信号是哪个控件发出来的
    • signal:信号的类型
    • receiver,method:信号如何处理
      • receiver:哪个对象负责处理
      • method:如何处理
    • type:暂不考虑,很少使用
  • 注意:connect 要求 sender 和 signal 参数是匹配的,sender是QPushButton,那么 signal 必须是 QPushButton 内置的信号(父类的信号)
  • 下面实现了一个点击按钮会关闭窗口的功能
cpp 复制代码
    QPushButton *button=new QPushButton(this);
    button->setText("关闭窗口");
    button->move(300,500);

    connect(button,&QPushButton::clicked,this,&Widget::close);
    //close是QWidget内置的槽函数,Widget继承自QWidget,也就继承了父类的槽函数

为什么要求传的是const char*类型的指针我传其他类型也可以?

  • Qt 5 开始,给 connect 提供了重载版本,在该版本中,第二个和第四个参数成了泛型参数,允许传入任意类型函数指针了

3. 自定义槽函数

3.1 第一种

  • 自定义槽:所谓的 slot 就是一个普通的成员函数
  • 所谓的自定义一个槽函数,操作过程和自定义一个普通的成员函数,没有区别
  • slots 是 Qt 自己扩展的关键字,在 Qt 5 之前槽函数要在 public slots: 后面写
  • Qt 里广泛使用了 元编程 技术(基于代码,生成代码),qmake 构建 Qt 项目的时候,就回调用专门的扫描器,扫描代码中特定的关键字(slots这种),基于关键字自动生成一大堆代码
cpp 复制代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QPushButton>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget() override;

    void handleClicked();

private:
    Ui::Widget *ui;
    QPushButton* button;
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"

#include <QPushButton>

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

    button=new QPushButton(this);
    button->setText("按钮");
    button->move(600,300);

    connect(button,&QPushButton::clicked,this,&Widget::handleClicked);
}

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

void Widget::handleClicked()
{
    //按下按钮,修改一下窗口标题
    this->setWindowTitle("按钮已经按下");
}

3.2 第二种

  • 除了通过 connect 来连接信号槽之外,还可以通过函数名字的方式来自动连接!
  • 这个窗口列出了 QPushButton 给我们提供的所有的信号(还包含了 QPushButton 父类的信号)
  • 点击要进行的操作后给我们直接生成好了一个函数,在里面直接编写对应代码即可
  • 生成的函数命名比较有特点,当函数名符合规则之后,Qt就能自动把信号和槽建立上联系
    • on 是固定前缀
    • pushButton是按钮的 objectName,clicked 是接收什么信号
  • 直接运行

4. 自定义信号

  • 用的比较少,Qt 内置的信号就足以应付大部分的开发场景了
  • 信号是一类特殊的函数,程序员只要写出函数声明,并且告诉 Qt,这是一个"信号"即可
    • 这个函数的定义,是 Qt 在编译过程中,自动生成的
  • 作为信号函数,这个函数的返回值,必须是 void
    • 有没有参数都可以,甚至可以支持重载
  • signals 是 Qt 自己扩展出来的关键字
    • Qt在编译的时候,扫描到 signals 这个关键字的时候,此时,就回自动的把下面的函数声明认为是信号,并且给这些写好函数自动的生成函数定义
  • emit 发射信号,也是 Qt 自己扩展出来的关键字
    • 其实不写emit,信号也能发射出去,不过为了可读性建议加上
  • 下方代码流程:
    • 点击按钮 =》QPushButton::clicked => Widget::on_pushButton_clicked() => emit mySignal() => void Widget::handleMySignal()
cpp 复制代码
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget() override;

signals:
    void mySignal();

public:
    void handleMySignal();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

// widget.cpp
#include "widget.h"
#include "ui_widget.h"

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

    // 建立连接,不代表信号发出来了!!
    connect(this,&Widget::mySignal,this,&Widget::handleMySignal);
}

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

void Widget::handleMySignal()
{
    this->setWindowTitle("处理自定义信号");
}

void Widget::on_pushButton_clicked()
{
    // 发送出自定义的信号
    // 发送信号的操作,也可以在任意合适的代码中,不一定非得在构造函数里
    emit mySignal();
}

5. 带参数的信号槽

  • 信号和槽也可以带参数,可以只用写参数类型
  • 当信号带有参数的时候,槽的参数必须和信号的参数一致
  • 此时发射信号时,就可以给信号函数传递实参,与之对应的这个参数就会被传递到对应的槽函数中
  • 这里的参数必须一致
    • 一致主要是要求类型,个数如果不一致也可以
    • 不一致的时候,要求信号的参数必须要比槽的参数个数要更多
  • 传参可以起到代码复用的效果,在不同场景中写入不同参数即可
  • 一个槽函数可能绑定多个信号
    • 如果严格要求参数个数一致,就意味着信号绑定到槽的要求就变高了
    • 不严格要求就能允许信号和槽之间的绑定更灵活了,更多的信号可以绑定到这个槽上
cpp 复制代码
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget() override;

signals:
    void mySignal(const QString &text,const QString &text2);

public:
    void handleMySignal(const QString &text);

private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

// widget.cpp
#include "widget.h"
#include "ui_widget.h"

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

    // 建立连接,不代表信号发出来了!!
    connect(this,&Widget::mySignal,this,&Widget::handleMySignal);
}

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

void Widget::handleMySignal(const QString& text)
{
    this->setWindowTitle(text);
}

void Widget::on_pushButton_clicked()
{
    // 发送出自定义的信号
    // 发送信号的操作,也可以在任意合适的代码中,不一定非得在构造函数里
    emit mySignal("把标题设置为标题1","");
}

void Widget::on_pushButton_2_clicked()
{
    emit mySignal("把标题设置为标题2","");
}

6. 信号和槽存在的意义

  • 设想很好
    • 1)解耦合:把触发 用户操作的控件 和 处理对应用户的操作逻辑 解耦合
    • 2)"多对多"效果
      • 一个信号,可以 connect 到多个槽函数上
      • 一个槽函数,也可以被多个信号 connect
  • 为了能够让信号和槽之间按照"多对多"的方式来进行关联

7. 信号槽补充

  • 使用 disconnect 来断开信号槽的连接
    • 使用方式和 connect 差不多
    • 大部分情况下,把信号和槽连上之后,就不必管了
    • 主动断开往往是把信号重新绑定到另一个槽函数上
cpp 复制代码
// widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick);
}

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

void Widget::handleClick()
{
    this->setWindowTitle("修改窗口标题");
    qDebug()<<"handleClick";
}

void Widget::handleClick2()
{
    this->setWindowTitle("修改窗口标题2");
    qDebug()<<"handleClick2";
}

void Widget::on_pushButton_2_clicked()
{
    // 1.先断开 pushButton 原来的信号槽
    disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick);
    // 2.重新绑定信号槽
    connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick2);
}
  • 使用lambda表达式定义槽函数
    • 需要使用变量捕获,永远不要使用[&] 捕获局部变量
cpp 复制代码
    QPushButton *button=new QPushButton(this);
    button->setText("按钮");
    button->move(200,200);

    connect(button,&QPushButton::clicked,this,[=](){
        qDebug()<<"lambda 被执行了!";
        button->move(300,300);
        this->move(100,100);
    });

8. 总结

  • 信号槽是啥 ~~ 尤其是和Linux的信号进行了对比
    • 信号源
    • 信号的类型
    • 信号的处理方式
  • 信号槽 使用
    • connect
  • 如何查阅文档
    • 一个控件,内置了哪些信号,信号都是何时触发
    • 一个控件,内置了哪些槽,槽都是什么作用
  • 自定义槽函数
    • 本质上就是自定义一个普通的成员函数
    • 还可以让 Qt Creatir自动生成,虽然没有显示connect,但是可以通过函数名字特定规则来完成自动连接
  • 自定义信号
    • 信号本质就是成员函数(函数定义是Qt自己生成的,咱们只需要写函数声明)
    • signals:自定义关键字
    • emit:完成信号的发射
  • 信号和槽还可以带参数
    • 发射信号的时候,把参数传递给对应的槽
    • 信号的参数和槽的参数要一致
      • 类型匹配
      • 个数,信号的参数要多于槽的参数
  • 信号槽存在的意义
    • 解耦合
    • 多对多效果
  • disconnect使用方式
  • lambda 表达式,简化槽函数的定义

小知识

click和clicked

  • click 是一个 slot 函数,作用就是在调用的时候相当于点击了一下按钮
  • clicked(过去分词形式,点完了),才是要触发的点击信号
    • 无参数的,clicked
    • 带参数的,clicked(bool),bool表示是否是被勾选的状态
相关推荐
郝学胜-神的一滴2 小时前
Python 多线程编程从入门到精通:原理+实战+最佳实践
开发语言·网络·python·pycharm
feng_you_ying_li2 小时前
C++11,lambda,包装器
开发语言·数据结构·c++
sycmancia2 小时前
Qt——布局管理区(二)
开发语言·前端·qt
傻啦嘿哟2 小时前
Python 操作 Word 页眉页脚完整指南
开发语言·c#
阿kun要赚马内2 小时前
Python装饰器的原理详解
开发语言·python
kyle~2 小时前
FANUC机械臂---R寄存器
开发语言·c++·机器人·fanuc
阿坤带你走近大数据2 小时前
OracleSQL优化案例_1
数据库·oracle
2201_756847332 小时前
uni-app怎么接极光推送 uni-app消息推送App端接入【教程】
jvm·数据库·python
一只大袋鼠2 小时前
MySQL 入门到单表操作超全总结(数据库 + SQL + 表操作 + 数据 CRUD)
数据库·mysql