Qt中的信号与槽

1:信号与槽概述

1.1:信号的本质

1.2:槽的本质

2:信号与槽的使用

2.1:连接信号与槽

2.1.1:纯代码方式

2.1.1.1:widget.h

2.1.1.2:widget.cpp

2.1.1.3:main.cpp

2.1.2:图形化方式

2.1.2.1:widget.h

2.1.2.2:widget.cpp

2.1.2.3:main.cpp

2.2:查看内置信号与槽

[2.3:通过Qt Creator生成槽代码](#2.3:通过Qt Creator生成槽代码)

2.3.1:First

2.3.2:Second

2.3.3:Third

2.3.4:Fourth

2.3.5:Fifth

2.3.6:Sixth

3:自定义信号与槽

3.1:自定义槽函数

3.1.1:语法

3.1.2:代码示例1

First:在widget.h声明自定义的槽函数

Second:在widget.cpp中实现槽函数,并且关联信号与槽

Third:运行结果

widget.h

widget.cpp

main.cpp

3.1.3:代码示例2

First:点击widget.ui文件,并且拖拽控件

Second:右击输入框,并选择转到槽​编辑

Third:编写自定义槽函数

Forth:运行结果

widget.h

widget.cpp

main.cpp

3.1.4:小结

3.2:自定义信号

3.2.1:基本语法

3.2.2:发送信号

3.2.3:代码示例

[First:在 widget.h 中声明自定义的信号,如图所示](#First:在 widget.h 中声明自定义的信号,如图所示)

[Second:在 widget.cpp 中实现槽函数,并且关联信号和槽](#Second:在 widget.cpp 中实现槽函数,并且关联信号和槽)

Third:运行结果

3.2.4:小结

4:带参数的信号和槽

4.1:代码示例一

[First:在 "widget.h" 头⽂件中声明重载的信号函数以及重载的槽函数](#First:在 "widget.h" 头⽂件中声明重载的信号函数以及重载的槽函数)

[Second:在 "Widget.cpp" ⽂件实现重载槽函数以及连接信号和槽](#Second:在 "Widget.cpp" ⽂件实现重载槽函数以及连接信号和槽)

Third:执行结果

widget.h

widget.cpp

main.cpp

4.2:代码示例二(信号槽参数列表匹配规则)

[First:在 "widget.h" 头文件中声明信号和槽函数.](#First:在 "widget.h" 头文件中声明信号和槽函数.)

[Second:在 "widget.cpp" 文件中实现槽函数以及连接信号和槽](#Second:在 "widget.cpp" 文件中实现槽函数以及连接信号和槽)

Third:运行结果

widget.h

widget.cpp

main.cpp

5:信号与槽的连接方式

5.1:一对一连接

5.1.1:一个信号连接一个槽

widget.h

widget.cpp

main.cpp

5.1.2:信号与信号进行连接

5.2:一对多连接

widget.h

widget.cpp

main.cpp

5.3:多对一连接

widget.h

widget.cpp

main.cpp

6:信号与槽的断开

6.1:代码示例

widget.h

widget.cpp

main.cpp

断开信号前

断开信号后

7:使用Lambda表达式定义槽函数

7.1:Lambda表达式的语法格式

[7.2:局部变量引入方式[ ]](#7.2:局部变量引入方式[ ])

7.3:代码示例1

widget.h

widget.cpp

main.cpp

7.4:代码示例2

widget.h

widget.cpp

main.cpp

7.5:代码示例3

widget.h

widget.cpp

main.cpp

7.6:函数参数()

7.7:选项Opt

widget.h

widget.cpp

main.cpp

7.8:Lambda表达式的返回值类型->

7.9:Lambda表达式的函数体{}

8:总结

9:知识补充


1:信号与槽概述

  • 在Qt中,用户和控件的每次交互过程称为⼀个事件。例如 "用户点击按钮" 是⼀个事件,"用户关 闭窗口" 也是⼀个事件。每个事件都会发出⼀个信号,例如用户点击按钮会发出 "按钮被点击" 的信号,用户关闭窗口会发出 "窗口被关闭" 的信号。
  • Qt 中的所有控件都具有接收信号的能力,⼀个控件还可以接收多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作.例如,按钮所在的窗口接收到"按钮被点击"的信号以后,会做出"关闭自己"的响应动作;再比如输入框自己接收到"输入框被点击"的信号后,会做出"显示闪烁的光标,等待用户输入数据"的响应动作.在Qt中,对信号做出的响应动作就称之为槽.
  • 信号与槽式Qt特有的消息传输机制,它能够将相互独立的控件关联起来.比如,"按钮"和"窗口"本身是两个独立的控件,点击"按钮"并不会对"窗口"造成任何影响.通过信号和槽的机制,可以将"按钮"和"窗口"关联起来,实现"点击按钮会使窗口关闭"的效果.

1.1:信号的本质

信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时 Qt 对应的窗口类会发出某个信号,以此对用户的操作做出反应.因此,**信号的本质就是事件.**例如

  • 窗口刷新
  • 鼠标移动、鼠标按下、鼠标释放
  • 键盘输入

那么在 Qt 中信号是通过什么形式呈现给使⽤者的呢?

  • 我们对哪个窗⼝进⾏操作, 哪个窗⼝就可以捕捉到这些被触发的事件。
  • 对于使⽤者来说触发了⼀个事件我们就可以得到 Qt 框架给我们发出的某个特定信号。
  • 信号的呈现形式就是函数, 也就是说某个事件产⽣了, Qt 框架就会调⽤某个对应的信号函数, 通知使⽤者。

在 Qt 中信号的发出者是某个实例化的类对象。

1.2:槽的本质

槽就是对信号响应的函数 .槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何位置,可以具有任何参数,可以被重载,也可以直接被调用(但是不能有默认参数).槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行.

  1. 信号和槽机制底层是通过函数间的相互调用实现的.每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数.例如: "按钮被按下" 这个信号可以⽤ clicked() 函数表示,"窗⼝关闭" 这个槽可以⽤ close() 函数表⽰,假如使⽤信号和槽机制.
  2. 信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于
  • 信号函数⽤signals 关键字修饰,槽函数⽤ public slots、protected slots 或者 private slots 修饰。signalsslots是 Qt 在 C++ 的基础上扩展的关键字,专⻔⽤来指明信号函数和槽函数.
  • 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)
    信号函数的定义是Qt自动在编译程序之前生成的**.** 编写 Qt 应⽤程序的程序猿⽆需关注.
    这种⾃动⽣成代码的机制称为 元编程 (Meta Programming) . 这种操作在很多场景中都能⻅
    到.

2:信号与槽的使用

2.1:连接信号与槽

在Qt中,QObject类提供了一个静态成员函数connect,该函数专门用来关联指定的信号函数与槽函数.

QObject 是 Qt 内置的父类. Qt 中提供的很多类都是直接或者间接继承⾃ QObject.

connect()函数原型:

cpp 复制代码
connect (const QObject *sender, 
 const char * signal ,
 const QObject * receiver , 
 const char * method , 
 Qt::ConnectionType type = Qt::AutoConnection )
  • sender:信号的发送者
  • signal:发送的信号类型
  • receiver:信号的接收者
  • method:接收信号的槽函数即这个对象该怎么处理.

2.1.1:纯代码方式

2.1.1.1:widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "QPushButton"

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);

    void handleClick();
    ~Widget();

private:
    Ui::Widget *ui;
    QPushButton * myButton;
};
#endif // WIDGET_H
2.1.1.2:widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    myButton = new QPushButton(this);
    myButton->setText("hello world");
    //第一个参数为该信号由哪个控件发出来的,第二个参数表示信号的类型,第三个参数表示信号的处理对象,第四个参数表示对象的处理方式
    connect(myButton,&QPushButton::clicked,this,&Widget::handleClick);
}

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

void Widget::handleClick()
{
    if(myButton->text() == QString("hello world"))
    {
        myButton->setText("Hello Qt");
    }
    else if(myButton->text() == QString("hello Qt"))
    {
        myButton->setText("hello World");
    }
}
2.1.1.3:main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

2.1.2:图形化方式

2.1.2.1:widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "QPushButton"

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);

    void handleClick();
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
2.1.2.2:widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"


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()
{
    if(ui->pushButton->text() == QString("Hello world"))
    {
        ui->pushButton->setText("Hello QT");
    }
    else
    {
        ui->pushButton->setText("Hello world");
    }
}
2.1.2.3:main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

2.2:查看内置信号与槽

有的uu可能会有以下两个问题

  1. 你怎么知道QPushButton有个clicked信号.
  2. 你怎么知道QWidget有一个close槽.

核心只有一个:多看官方文档.

我们通过观察QPushButton,发现里头并没有clicked信号,此时莫急,当没有找到时,我们就继续去父类去查找.因此我们去他的⽗类 QAbstractButton 中继续查找关键字signals.

  • 这⾥的clicked() 就是要找的信号。槽函数的寻找⽅式和信号⼀样,只不过它的关键字是slot .

2.3:通过Qt Creator生成槽代码

Qt Creator 可以快速帮助我们⽣成信号槽相关的代码

2.3.1:First

  • 新建项⽬,如下图为新建完成之后所包含的所有⽂件.

注意:创建时要生成ui文件.

2.3.2:Second

  • 双击 widget.ui ⽂件,进⼊ UI 设计界⾯.

2.3.3:Third

  • 在 UI 设计窗⼝中拖⼊⼀个 "按钮" ,并且修改 "按钮" 的名称及字体大小等;

2.3.4:Fourth

  • 可视化⽣成槽函数.


当单击 "转到槽..." 之后,出现如下界⾯:对于按钮来说,当点击时发送的信号是:clicked(),所以此处选择:clicked()

对于普通按钮来说, 使⽤ clicked 信号即可. clicked(bool) 没有意义的. 具有特殊状态的按
钮(比如复选按钮)才会⽤到 clicked(bool)

2.3.5:Fifth

  • ⾃动⽣成槽函数原型框架;

⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:
1、以 " on " 开头,中间使⽤下划线连接起来;
2、" XXX " 表⽰的是对象名(控件的 objectName 属性)。
3、" SSS " 表⽰的是对应的信号。
如:" on_pushButton_clicked() ",pushButton 代表的是对象名,clicked 是对应的信号。

  • 在 "widget.cpp" 中⾃动⽣成槽函数定义.

2.3.6:Sixth

  • 在槽函数函数定义中添加要实现的功能. 实现关闭窗⼝的效果.

3:自定义信号与槽

3.1:自定义槽函数

3.1.1:语法

在 Qt 中,允许⾃定义信号的发送⽅以及接收⽅,即可以⾃定义信号函数和槽函数。但是对于⾃定义的信号函数和槽函数有⼀定的书写规范。

  1. 早期的 Qt 版本要求槽函数必须写到 "public/private/protected slots" 下,但是现在⾼级版本的 Qt 允许写到类的 "public" 作⽤域中或者全局下;
  2. 返回值为 void,需要声明,也需要实现;
  3. 可以有参数,可以发生重载.

3.1.2:代码示例1

First:在widget.h声明自定义的槽函数

此处的slots是Qt中自己扩展的关键字(不是C++标准中的语法)

  • Qt中广泛包含了元编程技术(基于代码,生成代码).
  • qmake构建Qt项目的时候,就会调用专门的扫描器,扫描代码特定的关键字.
  • 基于关键字自动生成一大堆相关的代码.
Second:在widget.cpp中实现槽函数,并且关联信号与槽
Third:运行结果
widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();
public slots:
   void handleClicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>

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

    QPushButton * Button = new QPushButton(this);
    Button->setText("按钮");
    Button->move(200,200);

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

void Widget::handleClicked()
{
    this->setWindowTitle("按钮已经被按下");
}

Widget::~Widget()
{
    delete ui;
}
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

3.1.3:代码示例2

First:点击widget.ui文件,并且拖拽控件
Second:右击输入框,并选择转到槽

当点击确定后,Qt Creator直接给我们生成好了一个函数.只需要在其内部编写代码即可

Third:编写自定义槽函数
Forth:运行结果
widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

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

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

void Widget::on_pushButton_clicked()
{
    this->setWindowTitle("Hello Qt");
}
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

3.1.4:小结

  • 如果我们通过图形化界面创建控件,还是推荐上面这种快速的方式来连接槽.
  • 如果是通过代码的方式来创建控件,那么就需要手动连接connect.

3.2:自定义信号

Qt中也允许自定义信号,但是其实比较少见,实际开发中很少会需要自定义信号,因为

  1. 信号就对应到用户的某个操作~
  2. 在GUI,用户能够进行哪些操作,是可以无限枚举的,Qt内置的信号,已经基本覆盖了用户所有可能的操作.
  3. 因此使用Qt内置的信号,就足以应付大部分的开发场景了.

3.2.1:基本语法

  1. ⾃定义信号函数必须写到 "signals" 下.
  2. 返回值为 void,只需要声明,不需要实现.
  3. 可以有参数,也可以发⽣重载.

3.2.2:发送信号

使用"emit" 关键字发送信号 。"emit" 是⼀个空的宏。"emit" 其实是可选的,没有什么含义,只是为了提醒开发⼈员。

3.2.3:代码示例

First:在 widget.h 中声明自定义的信号,如图所示
Second:在 widget.cpp 中实现槽函数,并且关联信号和槽
Third:运行结果

3.2.4:小结

  • 将信号与槽建立了连接,不代表信号发送出来了!
  • 那么如果才能发送自定义的信号呢,Qt内置的信号都不需要我们手动通过代码来触发
  • 用户在GU进行某些操作,就会自动触发对应的信号(发送信号的代码已经内置到Qt框架中了).

4:带参数的信号和槽

Qt的信号和槽也支持带有参数,同时可以支持重载.

  • 当信号带有参数的时候,槽的参数必须和信号的参数一致.
  • 参数要求一致,主要是要求参数的类型得一致,个数不一致也可以.
  • 但是当参数个数不一致的时候,要求信号的参数个数必须比槽函数的参数个数要多一些.

4.1:代码示例一

First:在 "widget.h" 头⽂件中声明重载的信号函数以及重载的槽函数

Second:在 "Widget.cpp" ⽂件实现重载槽函数以及连接信号和槽

Third:执行结果

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();
signals:
    //信号函数声明
    void MySignal();
    //信号函数的重载
    void MySignal(const QString &);
public slots:
    //槽函数的声明
    void MySlot();
    //槽函数的重载
    void MySlot(const QString &);

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    void (Widget::*Psignal)(const QString &)= &Widget::MySignal;
    void (Widget::*Pslot)(const QString &)= &Widget::MySlot;
    //链接信号与槽
    connect(this,Psignal,this,Pslot);
}

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

void Widget::MySlot()
{
    qDebug()<<"调用Myslot()";
}

void Widget::MySlot(const QString &)
{
    qDebug()<<"调用Myslot(const QString &)";
}

void Widget::on_pushButton_clicked()
{
    emit MySignal("hello world");
}
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

4.2:代码示例二(信号槽参数列表匹配规则)

First:在 "widget.h" 头文件中声明信号和槽函数.

Second:在 "widget.cpp" 文件中实现槽函数以及连接信号和槽

Third:运行结果

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();
signals:
    void MySignal(const QString & text);
public slots:
    void Myslots(const QString & text);
private slots:
    void on_pushButton_clicked();
    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
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::Myslots);
}

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

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


void Widget::on_pushButton_clicked()
{
    emit MySignal("这是标题1");
}


void Widget::on_pushButton_2_clicked()
{
    emit MySignal("这是标题2");
}
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

那么我们思考下,应该是要求信号的参数跟槽函数的参数一致,那么这里为什么还会允许信号的参数比槽函数的参数个数多呢

  • 因为一个槽函数可能绑定了多个信号.
  • 如果严格要求一致,就意味着信号绑定到槽函数的要求变高了,换而言之,在当下这样的规则下,就允许信号与槽之间的绑定更加灵活了,更多的信号可以绑定在槽函数上了.
  • 个数不一致时,槽函数会按照参数顺序,拿到信号的前面N个参数,至少要确定,槽函数的每个参数都是有值的.

5:信号与槽的连接方式

5.1:一对一连接

主要是分别有两种形式,分别是

  1. 一个信号连接一个槽.
  2. 一个信号连接一个信号.

5.1.1:一个信号连接一个槽

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();
signals:
    void Mysignal();
public:
    void HandleMysignal();
private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
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()
{
    qDebug()<<"接收到了信号";
}

void Widget::on_pushButton_clicked()
{
    //发送信号
    emit Mysignal();
}
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

5.1.2:信号与信号进行连接

信号与信号的链接,只需要在上面代码的基础上添加下面的代码即可~

5.2:一对多连接

一个信号连接多个槽

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void EmitSignal();
signals:
    void Mysignal();
public slots:
    void Myslots1();
    void Myslots2();
    void Myslots3();
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton * Btn = new QPushButton("按钮",this);
    //链接信号与槽
    connect(this,&Widget::Mysignal,this,&Widget::Myslots1);
    connect(this,&Widget::Mysignal,this,&Widget::Myslots2);
    connect(this,&Widget::Mysignal,this,&Widget::Myslots3);
    //进行链接
    connect(Btn,&QPushButton::clicked,this,&Widget::Mysignal);
}

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

void Widget::Myslots1()
{
    qDebug()<<"第一个信号";
}

void Widget::Myslots2()
{
    qDebug()<<"第二个信号";
}

void Widget::Myslots3()
{
   qDebug()<<"第三个信号";
}

void Widget::EmitSignal()
{
    emit Mysignal();
}

main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

5.3:多对一连接

  • 多个信号连接多个槽
widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();
signals:
    void Mysignal1();
    void Mysignal2();
public:
    void Myslot();
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(this,&Widget::Mysignal1,this,&Widget::Myslot);
    connect(this,&Widget::Mysignal2,this,&Widget::Myslot);
    emit Mysignal1();
    emit Mysignal2();
}

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

void Widget::Myslot()
{
    qDebug()<<"Myslot";
}
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

  • PS:同样可以进行多对多连接,这里博主就不演示啦,uu们下去后可以自己自行演示下~

6:信号与槽的断开

信号与槽函数之间是可以通过disconnect函数来断开信号与槽的连接.

PS:disconnect函数用的还是比较少的,大部分情况下,把信号与槽函数连接上了以后,就不必再管了,主动断开往往是把信号重新绑定到另一个槽函数上.

6.1:代码示例

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();
public:
    void Myslots();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton * Btn = new QPushButton("按钮",this);
    Btn->move(200,200);
    connect(Btn,&QPushButton::clicked,this,&Widget::Myslots);
    disconnect(Btn,&QPushButton::clicked,this,&Widget::Myslots);
}

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

void Widget::Myslots()
{
    this->setWindowTitle("Hello world");
}
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

断开信号前

断开信号前,我们点击按钮,此时窗口的标题会显示hello world.

断开信号后

当断开信号后,此时我们无论如何点击按钮,窗口的标题都不会发生改变.

7:使用Lambda表达式定义槽函数

如果我们想方便地编写槽函数,比如在编写函数时连函数名都不想定义,则可以通过Lambda表达式来达到这个目的.

Lambda表达式的本质是一个"匿名函数",主要应用在"回调函数"的场景中.

7.1:Lambda表达式的语法格式

bash 复制代码
[capture] (params) opt->ret
{
    Function body;
};

|-------------------|-----------|
| capture | 捕获列表 |
| params | 参数表 |
| opt | 函数选项 |
| ret | 返回值类型 |
| Function body | 函数体 |

7.2:局部变量引入方式[ ]

  • [ ]:标识一个Lambda表达式的开始,不可忽略

|-------------|----------------------------------------|
| 符号 | 说明 |
| [] | 局部变量捕获列表.Lambda表达式不能访问外部函数体的任何局部变量 |
| [a] | 在函数体内部使用值传递的方式访问变量a |
| [&a] | 在函数体内部使用引用传递的方式访问变量a |
| [=] | 函数外的所有局部变量都通过值传递的方式使用,函数体内使用的是副本 |
| [&] | 以引用的方式使用Lambda表达式外部的所有变量. |
| [=,&foo] | foo使用引用方式,其余则是值传递的方式. |
| [&,foo] | foo使用值传递方式,其余使用引用传递 |
| [this] | 在函数内部可以使用类的成员函数与成员变量,=和&形式也都会默认引入. |

  • 由于使⽤引⽤⽅式捕获对象会有局部变量释放了而Lambda函数还没有被调⽤的情况。如果执行Lambda函数,那么引⽤传递⽅式捕获进来的局部变量的值不可预知。所以绝⼤多数场合使⽤的形式为: [=] () { }
    • 早期版本的 Qt,若要使⽤Lambda表达式,要在 ".pro" ⽂件中添加: CONFIG += C++11
      因为 Lambda表达式 是 C++11 标准提出的。Qt5 以上的版本⽆需⼿动添加,在新建项⽬时会⾃动添加。

7.3:代码示例1

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton * Btn = new QPushButton("按钮",this);
    [=]()
    {
        Btn->setText("测试Lambuda表达式");
    }();
}

Widget::~Widget()
{
    delete ui;
}
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

7.4:代码示例2

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton * Btn1 = new QPushButton("按钮1",this);
    QPushButton * Btn2 = new QPushButton("按钮2",this);
    Btn2->move(200,200);
    [=]()
    {
        Btn1->setText("测试按钮1");
        Btn2->setText("测试按钮2");
    }();
}

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

main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

7.5:代码示例3

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton * Btn1 = new QPushButton("按钮1",this);
    QPushButton * Btn2 = new QPushButton("按钮2",this);
    Btn2->move(200,200);
    [Btn1,Btn2]()
    {
        Btn1->setText("测试按钮1");
        Btn2->setText("测试按钮2");
    }();
}

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

main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

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

7.6:函数参数()

(params) 表示Lambda函数对象接收的参数,类似于函数定义中的⼩括号表⽰函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引⽤(如:(int &a,int &b))两种⽅式进⾏传递。函数参数部分可以省略,省略后相当于⽆参的函数。

7.7:选项Opt

Opt 部分是可选项,最常⽤的是 mutable声明 ,这部分可以省略
Lambda表达式外部的局部变量通过值传递进来时,其默认是 const ,所以不能修改这个局部变量的拷贝,加上mutable 就可以修改.

widget.h
cpp 复制代码
#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:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton * Btn1 = new QPushButton("Btn1",this);
    QPushButton * Btn2 = new QPushButton("Btn2",this);

    int m = 10;
    Btn1->move(300,100);
    Btn2->move(500,100);

    connect(Btn1,&QPushButton::clicked,this,[m]()mutable{m = 20;qDebug()<< m;});
    connect(Btn2,&QPushButton::clicked,this,[=](){qDebug()<< m;});
}

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

main.cpp

cpp 复制代码
#include "widget.h"

#include <QApplication>

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

7.8:Lambda表达式的返回值类型->

可以指定 Lambda表达式 返回值类型;如果不指定返回值类型,则编译器会根据代码实现为函数推导⼀个返回类型;如果没有返回值,则可忽略此部分.

7.9:Lambda表达式的函数体{}

Lambda表达式的函数体部分与普通函数体⼀致。⽤ { } 标识函数的实现,不能省略,但函数体可以为空.

8:总结

那么到这里,Qt中的信号与槽的核心内容博主就讲完啦,接下来我们来小结一下

1:首先学习了信号槽是啥.

  1. 信号源
  2. 信号的类型
  3. 信号的处理方式

2:然后再是信号槽的使用------>connect函数.

3:如何查阅文档

  1. 一个控件,内置了哪些信号,信号都是何时触发.
  2. 一个控件,内置了哪些槽,槽都是什么作用.
  3. 很可能需要的信号槽,需要到该类的父类/爷爷类/祖宗类去进行查询.

4:自定义槽函数

  1. 本质上就是自定义了一个普通的成员函数.
  2. 还可以让Qt Creator自动生成(虽然没有显式connect,但是可以通过函数名字特定规则来完成自动连接).

5:自定义信号

  1. 信号的本质是成员函数(函数的定义是Qt自己生成的,我们只需要写函数声明)
  2. signals:自定义关键字中
  3. emit来完成信号的发射(emit也可以省略)

6:带参数的信号与槽

  1. 发射信号的时候,把参数传递给对应的槽.
  2. 信号的参数和槽的参数要一致.
  3. 首先是类型要匹配.
  4. 其次是信号的参数个数要多于槽函数的参数个数.

7:信号与槽的存在意义

  1. 解耦合
  2. 连接方式

8:disconnect函数的使用方式.

9:Lambda表达式,简化槽函数的定义.

9:知识补充

这里博主给uu们详细讲解下什么是耦合与内聚

平常我们写代码时,要追求"高内聚,低耦合",这里博主通过一个故事来讲解

  • 小明,有一个女神,是他的高中同桌,他两的关系好到让小明以为他就是女神关系最好的异性朋友,然而到了大学,他们是在不同的省份上大学,但是小明依旧对女神穷追猛打,可最终换来的还是一张"好人卡",甚至小明发现他还是一个备胎,后面过了很多年,小明也找到了自己的爱妻,与他高中女神的关系也就是一个朋友圈的点赞之交.
  • 在去年有一次,小明的爱妻生病了,得了胆结石,发作比较疼,那么就要去医院做手术,把胆切掉,对小明来说,他就得把手头上的工作暂停,跟他上班的公司请假一段时间去专门照顾他的媳妇.
  • 那么很明显,小明的媳妇生病了,对他就会有十分明显的影响,这放在计算机里头就是高耦合,一个模块出了大问题会影响到另外一个模块.
  • 但是反过来,如果是小明的高中女神生病了,也要去医院做手术,对小明来说,顶多就是给他女神的朋友圈点个赞,然后小明该干嘛还是干嘛.
  • 那么很明显,女神生病,对小明来说,没有很大的影响,这放在计算机里头就是低耦合,一个模块出了问题对另一个模块影响甚微.
  • 小明的媳妇,有一个比较典型的缺点,那就是拿到什么东西就会随手一丢,衣服就会丢得导出都是,衣服可能在沙发上,可能在床上,可能在地上,可能在椅子上,也可能在沙发缝里头,总之就是不在衣柜里头,那么小明为了找到某一件衣服,小明就需要把家里的每一个角落都遍历一遍.而这就是低内聚
  • 低内聚**:写代码的时候,实现某个功能点,围绕这个功能的相关被放到整个项目的各个地方.**
  • 但是如果小明的媳妇十分有纪律,衣服都是放在固定的地方,要么衣柜里头,要么在脏衣篓里头,此时要找到某个衣服就十分好找了,这就是高内聚.
  • 高内聚**:写代码的时候,某个功能点的实现都被集中放到了一起.**
相关推荐
刺客xs1 天前
Qt ----- QT线程
开发语言·qt
SunkingYang1 天前
QT程序如何将事件和消息发送给MFC程序,MFC程序如何接收消息和事件
qt·mfc·消息·事件·通信·通讯·传递
凯子坚持 c1 天前
Qt 5.14.0 入门框架开发全流程深度解析
开发语言·qt
深蓝海拓1 天前
PySide6从0开始学习的笔记(十四)创建一个简单的实用UI项目
开发语言·笔记·python·qt·学习·ui·pyqt
小尧嵌入式1 天前
Linux网络介绍网络编程和数据库
linux·运维·服务器·网络·数据库·qt·php
海涛高软1 天前
Qt中使用QListWidget列表
开发语言·qt
010米粉0101 天前
Qt之构建方式
qt
凯子坚持 c1 天前
Qt 信号与槽机制深度解析
开发语言·qt
世转神风-1 天前
qt-初步编译运行报错-When executing step “Make“-无法启动进程“make“
开发语言·qt
一然明月2 天前
QT之基础控件
开发语言·qt