Qt信号与槽

信号 Signal 和 槽 Slot 概念

系统内部的通知机制,进程间通信的方式
信号源:由哪个控件发送的信号
信号的类型:用户进行不同的操作,就可能触发不同的信号,比如:输入框的光标,点击按钮等
信号的处理方式:注册信号处理函数 在Qt中把这种函数称为槽,一个信号和一个槽关联起来,本质上也是一个回调函数,后续只要信号触发了,Qt就会自动的执行槽函数

connect函数

是QObject提供的静态函数,
在这里插入图片描述
这里的QObject就是Qt内置类的“祖宗类”,都间接或直接的继承QObject

例子:编写一个按钮实现点击关闭窗口

在这里插入图片描述

这里的click是一个slot函数,作用就是在调用的时候相当于点击了按钮,而这里的clicked表示点击后的触发信号,注意:connect要求,这两个参数是匹配的,button的类型如果是QPushButton* 此时,第二个参数的信号必须是QPushButton内置的信号(父类的信号),不能是一个其他的类,比如QLineEdit的信号
connect(button,&QPushButton::clicked,this,&Widget::close);这里的close是Widget内部的槽函数,作用就是关闭当前的控件
完整代码:

#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::close);
}

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

connect函数的参数列表:

这里有一个疑问?
在这里插入图片描述
在参数列表中,用来接收函数指针的参数为什么类型写作为const char* 类型?函数指针不是 函数名(*)()这样的一个形式吗?
在c++中,不允许你使用两个不同的指针类型,相互赋值(函数传参,本质上就是赋值)
上面的函数声明是之前qt的旧版本,对于旧版本而言传信号和槽函数指针的时候要加上 SIGANL和SLOT这样的宏定义
connect(button,SIGAL(&QPushButton::clicked),this,SLOT(&Wideget::close))
Qt 5 开始,对上述写法做出了简化.不再需要写 SIGNAL 和 SLOT 宏了.给 connect 提供了重载版本,重载版本中,第二个参数和第四个参数成了 泛型参数,允许咱们传入任意类型的函数指针了。
在这里插入图片描述
此时connect函数就带有一定的参数检查功能,如果你传入的第一个参数和第二个参数不匹配或者第三和第四(你要指定信号和发送信号是同一个类型的,接收同理)不然就会编译报错

自定义信号和自定义槽

其实所谓的槽只是普通的成员函数,自定义一个槽函数写一个成员函数没有区别。

第一种自定义槽函数的方式:

实例代码:
widget.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;
}

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:
    Widget(QWidget *parent = nullptr);
    void handleClicked();
    ~Widget();

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

第二种自定义槽函数的方式:

在ui界面中右击按钮点击转到槽
在这里插入图片描述
这个窗口就列出了QPushButton给我们提供所有的信号(还包含了QPushButton父类的信号)
在这里插入图片描述
点击后直接生成了on_pushButton_clicked()槽函数
在这里插入图片描述
直接在这里面编写按钮点击后要实现的逻辑即可。

在QT中,除了通过connect来连接信号槽之外,还可以通过函数名字的方式来自动连接!!只需满足下面的规则
在这里插入图片描述
自定义信号:比较少见,实际开发中会很少需要自定义信号,信号对应着用户的某个操作,在Qt中的内置信号,就基本覆盖了大部分的用户操作。
1.其实信号是一类特殊的函数,程序员只要写出函数声明,并且告诉Qt,这是一个“信号”即可,这个函数的定义会在Qt的编译过程中自动生成的。(自动生成的过程,程序员不能干预),因为信号是Qt的特殊机制,Qt生成的信号函数的实现,要配合Qt框架做很多既定的操作
2.作为信号函数,这个函数的返回值,必须是void,有没有参数都可以,也可以支持函数重载

自定义信号需要使用到signals:关键字,当Qt扫描到类中包含signals这个关键字的时候,此时,就会自动的把下面的函数声明认为是信号,并且给这些信号函数自动的生成函数定义。

编写自定义信号代码示例:

#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 handleMySignal();
signals:
    void mySignal();

private slots:
    void on_pushButton_clicked();

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

#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,&)
    connect(this,&Widget::mySignal,this,&Widget::handleMySignal);
}

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

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


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

但是这里并没有把窗口标题改成处理自定义信号,为什么呢? 因为信号没有发射,建立连接不代表信号发出来了!对于Qt的内置信号,是不需要我们手动写代码触发的,用户在GUI,进行某种操作,就会自动触发对应信号.(发射信号的代码已经内置到Qt框架中了)
只需在你想要发送信号的位置中加上一句emit mySignal();即可, emit + 发送信号的函数。
其实在 Qt 5 中 emit 现在啥都没做,真正的操作都包含在 mySignal 内部生成的函数定义了,即使不写 emit, 信号也能发出去!!,即使如此, 实际开发中, 还是建议大家,把 emit 都加上吧~~加上代码可读性更高,更明显的标识出,这里是发射自定义的信号了。

带参数的信号和槽

通过带参的方式来实现数据传递,当信号带有参数的时候,槽的参数必须和信号的参数一致,此时发射信号的时候,就可以给信号函数传递实参,与之对应的这个参数就会被传递到对应的槽函数中。

代码演示:
widget.h
在这里插入图片描述
这里的参数必须类型一致,个数可以不一致,但是在不一致的情况下,要求信号的个数必须要比参数个数要多(C++中声明函数的时候可以不用写函数名字)
在这里插入图片描述
传参可以起到复用代码的效果有多个逻辑,逻辑上整体一致,但是涉及到的数据不同就可以通过函数-参数来复用代码,并且在不同的场景中传入不同的参数即可
代码示例:
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:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void handleMySignal(const QString&);//自定义信号的槽函数
signals:
    void mySignal(const QString&);//自定义信号的信号函数

private slots:
    void on_pushButton_clicked();//按钮点击信号的槽函数

    void on_pushButton_2_clicked();

    void on_pushButton_3_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(ui->pushButton,&QPushButton::clicked,this,&)
    connect(this,&Widget::mySignal,this,&Widget::handleMySignal);
    //emit mySignal();//程序启动则发生信号,发送信号的操作可以在其他地方写都可以
}

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

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

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

void Widget::on_pushButton_2_clicked()
{
    emit mySignal("带参数的信号");
}

void Widget::on_pushButton_3_clicked()
{
    emit mySignal("带参数的信号2");
}

主体逻辑:写了一个自定义信号mySignal和与他对应的槽函数handleMySignal槽函数,然后有两个按钮对应两个点击信号的槽函数,对于自定义信号来说,不仅要有对应的槽函数还需要自己去实现发送信号的逻辑,所以这里的写法就是,用按钮的槽函数发送信号,从而实现调用自定义信号槽函数,而这里的自定义信号和槽函数都是有参数的,我们可以在发送信号的时候给定不同的参数,复用槽函数代码实现不同的效果。

在Qt中很多内置的信号,也是带有参数的.(这些参数不是咱们自己传递的) 比如:chilcked信号就带有一个参数clicked(bool)
这个bool参数用于记录按钮是否处于“选中”状态,这个选中状态对于QPushButton没有太大作用,是用于之后的QCheckBox复选框。

注意1:信号函数的参数个数超过了槽函数的参数个数,此时,都是可以正常使用的,信号函数的参数个数少于槽函数的参数个数,此时的代码是无法编译的,为什么呢?因为我们严格要求个数一致,就意味着信号绑定到槽的要求就变高了。换而言之,当下这样的规则,就允许信号和槽之间的绑定更加灵活了,更多的信号可以绑定到槽函数上。

注意2:带有参数的信号,要求信号参数和槽的参数要一致

注意3:Qt中如果要让某个类能够使用信号槽(可以在类中定义信号和槽函数)则必须在类最开始的地方,写下Q_OBJECT宏

信号和槽的断开

使用disconnectl来断开信号和槽的连接,一般情况下是不会怎么去使用,大多用于断开后绑定到另一个槽函数上。
实现功能:用一个按钮的点击槽函数去取消连接另一个信号和槽的连接
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:
    Widget(QWidget *parent = nullptr);
    void handleClick();
    void handleClick2();
    ~Widget();

private slots:
    void on_pushButton_2_clicked();

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

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);
}

void Widget::handleClick()
{
    setWindowTitle("title1");
    qDebug()<<"handleClick";
}

void Widget::handleClick2()
{
    setWindowTitle("title2");
    qDebug()<<"handleClick2";
}

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

void Widget::on_pushButton_2_clicked()
{
    disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick);
    connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick2);
    qDebug()<<"agga";
}

在这里插入图片描述
如果没有disconnect就去连接另外一个槽函数就会导致,一对多的现象,就是一个信号实现两个逻辑同时调用两个槽函数。

使用lambda表达式定义槽函数

本质就是一个“匿名函数”,主要应用于在回调函数的场景中一次性使用。
示例代码:

#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,[](){
        qDebug()<<"lambda";
    });
}

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

但是对于lambda表达式中是无法获取上层作用域的变量的,lambda为了解决上述问题,引入了“变量捕获”语法,通过变量捕获,获取到外层作用域中的变量。就是在【】中输入你想要捕获的变量名,在【】输入等号=即可获取上层作用域中所有的变量名都可捕获进来。后续如果我们的槽函数比较简单或者一次性使用的,就经常会写作这种lambda的形式。另外也要确定lambda上层作用域的变量的生命周期,一定要确定lambda使用的变量一定是可用的(没有被释放的)。

示例代码:

#include "widget.h"
#include "ui_widget.h"
#include "QDebug"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("按钮2");
    connect(button,&QPushButton::clicked,this,[button](){
        qDebug()<<"lambda";
        button->move(200,400);
    });
}

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


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值