Qt——信号 和 槽

目录

概述

信号和槽的使用

自定义信号和槽

带参数的信号和槽


概述

        在Linux系统中,我们也介绍了信号的产生、信号的检测以及信号的处理机制,它就是系统内部的通知机制,也可以是一种进程间通信的方式。在系统中有很多信号,我们可以通过signal()函数捕捉信号,重写一个信号处理函数。在Qt中的信号也和系统中的信号有相似之处。

        Qt中的信号也要涉及信号三要素:信号源、信号类型和信号处理方式

  • 信号源:Qt中的信号是由某个控件发出的,Linux系统中可以是一个进程发送的信号。
  • 信号类型:用户不同的操作会触发不同的信号,例如点击按钮就会触发点击信号(clicked)、在输入框中移动光标也会触发相应的信号,我们写这样的GUI程序就是为了和用户交互,所以必须知道当前用户的具体操作,通过不同的操作进行不同的处理
  • 信号处理方式:在Qt中就引入了一个概念就是槽(slot),说白了就是一个函数,再使用connect这样的函数把一个信号和一个槽关联起来,之后只要触发了信号,Qt就会自动执行槽函数,这种槽函数本质上也是一种回调函数。

        所以在Qt中一定要先关联信号和槽,也就是先要有槽函数并使用connect函数将二者关联起来,然后再触发这个信号,顺序不能颠倒。


信号和槽的使用

        在Qt中,QObject类提供了一个静态成员函数,就是connect(),这个函数就是关联信号和槽函数的。

        而且Qt中的类本身也存在着一定的继承关系,比如我们使用的QLabel、QPushButton、QLineEdit等,他们的父类都是QWidget,也就是我们创建项目时选择的要继承的类,这个QWidget类也是继承了QObject这个类。所以Qt中基本所有的类都可以使用connect()这个函数。

QMetaObject::Connection QObject::connect(const QObject *sender,\
                                         const char *signal,\
                                         const QObject *receiver,\
                                         const char *method,\
                                         Qt::ConnectionType type = Qt::AutoConnection)

        这个函数上一篇我们已经介绍过了,再来说一下细节:

  • 前两个参数必须要匹配,比如是QPushButton对象发出的,那也必须是QPushButton内置的信号。
  • 第一个和第三个参数我们传入的是QObject的子类指针,所以没有问题,但是第二个和第四个参数传入的是函数指针,但是为什么使用char*来接收呢?这两个的意义可不一样.
  • 原因就是这是旧版本的connect函数声明,函数传参是时候需要给这两个参数分别放在SIGNAL()和SLOT()的括号中,通过宏替换就可以把函数指针转换成char*.
  • 所以从Qt 5开始,做出了简化,不需要写这两个宏了,给connect函数提供了重载版本。
    template <typename Func1, typename Func2>
    static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,\
                                                  Func1 signal,\
                                                  const typename QtPrivate::FunctionPointer<Func2>::Object *receiver,\
                                                  Func2 slot,\
                                                  Qt::ConnectionType type = Qt::AutoConnection)
  • 这就是为什么传参的时候一定要匹配,这样也有了一定的参数检查功能。

        我们再写一个场景来使用一下。

// 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(300, 200);

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

Widget::~Widget()
{
    delete ui;
}
  • 创建一个QPushButton对象,想要点击这个按钮后就关闭这个窗口,发出信号的一定是QPushButton对象。
  • 发出的信号一定是QPushButton内置的clicked信号
  • 要交给的对象就是this也就是Widget对象
  • 处理函数就可以使用Widget继承的QWidget内置的close()槽函数

        运行后就可以通过点击这个按钮关闭这个界面了。

        

        但是像clicked这样的信号还有多少呢,想要知道就要看看文档了。

        

        我们打开这个程序就可以查看文档,在文档的索引框,查找QPushButton类。

        找了一遍后发现没有找到clicked这样的信号,既然这样就要向它的父类中查找。

        QPushButton的父类就是QAbstractButton,通过类名我们也可以得知这是一个抽象类,在这个页面向下查找就找到了Signals这块,这里就有clicked等信号,也就类似于函数。


自定义信号和槽

        有了上述的介绍,我们现在就可以自己写一个信号处理动作,这也是上一篇也用过的,先使用纯代码的方式实现一下自定义槽

  • 还是要先new一个QPushButton对象,把点击信号和槽函数关联起来。
  • 槽函数中就设置为,捕捉到了信号就把Widget界面的标题修改一下。

        

        下面我们再来看一下通过图形化界面的方式设置信号和槽。

  1. 打开Qt Disigner,通过拖拽的方式把Push Button控件添加到界面。
  2. 可以编辑一下按钮文本,然后右键点击控件,再点击转到槽
  3. 点击后就可以看到Push Button提供的信号,甚至还有它的父类的信号,常用的还是clicked信号
  4. 双击clicked就会跳转到编辑widget.cpp的界面,此时函数的声明和定义都已经自动生成好了,编写代码即可。

【注意】:这种方式是不会出现connect()函数的,Qt中除了connect可以连接信号和槽外,还可以通过函数名字的方式来自动连接,如上图所示:

  • on作为固定前缀
  • pushButton为控件的objectName
  • clicked为信号名字

当符合规则后,Qt就可以把信号和槽自动建立好,所以函数名必须是正确的,如果不正确就无法连接。

        这里名字故意写错,就不能达到我们想要的效果,这都是Qt中调用connectSlotsByName这个函数触发自动连接信号和槽,这个函数是在自动生成的ui_widget.h中setupUi函数中调用的。

        所以,如果是通过图形化界面创建控件,还是使用第二种方式快速连接信号和槽;如果是通过代码的方式创建控件,还是得手动调用connect函数,原因就是没有调用connectSlotsByName函数。

        虽然Qt中允许自定义信号,但是比较少见,开发过程中很少自定义信号,在GUI界面的操作是可以穷举出来的,Qt中内置的信号基本上已经覆盖了,所以使用Qt内置的信号就足够了。

        Widget类虽然没有定义任何信号,但是它继承了QWidget和QObject,所以他们两个的信号可以直接使用。

        信号本质上也是一个函数,它是是一个特殊的函数,只需要写出函数声明,告诉Qt这是一个信号即可,这个函数的定义在编译过程中是自动生成的。

        而且信号在Qt中是特殊的机制,Qt生成的信号函数的实现也要配合Qt框架做其他既定操作。

        作为信号函数,返回值必须是void,有无参数都可以,也可以支持重载。

        signals是Qt自己扩展的关键字,qmake会调用代码分析和生成的工具,识别到signals这个关键字时就会把下面的函数声明认为是信号,并自动生成函数定义。

        通过connect函数连接信号和槽函数就可以了,但是光连接还是不够的,还需要发送我们自定义的信号,这个emitu也是Qt中的关键字,作用就是发送信号

        或者我们把信号从构造函数拿出来,当点击按钮,就会触发on_pushButton_clicked函数,函数中就会发送mySignal信号,收到信号就会执行handleSignal自定义函数,之后就会修改窗口标题。


带参数的信号和槽

        上述的不管是我们自定义的还是Qt内置的信号和槽都是不带参数的,但是他们也可以带参数,并且信号和槽的参数必须一致,此时触发信号的时候,就可以给信号函数传递实参,这个参数就会被传递到对应的槽函数中。

        参数的类型必须要一致,个数不一致是可以的,但是要求信号的参数的个数必须要比槽的参数多。

        通过这一次连接信号和槽,并搭配不同参数就可以实现不同结果,可以让代码复用,就比如:

        而且信号的参数比槽函数多也是可以的,但是不允许槽函数中的参数比信号中的多,原因就是:

  • 一个槽函数可能绑定多个信号。
  • 如果严格要求就意味着信号绑定到槽的要求变高了。
  • 所以信号的参数个数可以大于槽的参数个数,这样让信号和槽之间的绑定变得更灵活更多的信号可以绑定到这个槽函数上
  • 虽然个数不一致,但是槽函数会按照参数顺序拿前N个参数,这样就可以确保槽函数的每个参数都有值

        还有一点就是,如果想要使用信号和槽的机制,就必须在类的一开始写上Q_OBJECT宏。

        当我们转到定义就可以看到这个宏中展开的代码,这里也有很多的宏,也会继续展开,最终实现相应的功能。

        

        信号和槽最主要就是要解决响应用户的操作,它在GUI框架中是一个有特色的机制,但是它的实现就没有那么简洁,但是它还要这样做就是为了:

  • 触发用户操作的控件 和 处理用户的操作逻辑 解耦合
  • 也想起到多对多的效果一个信号可以connect到多个槽函数上,一个槽函数也可以被多个信号connect。

这就是可以把多个信号和多个槽函数绑定到一起。

【补充】:

  1. 可以使用disconnect断开信号和槽的连接。但是大部分情况下,把信号和槽连接好就不用管了,主动断开的情况就是要把信号重新绑定到另一个槽函数上,如果不断开,这个信号发送就会触发两个槽函数。断开连接前,标题修改为旧标题,当点击下方pushButton后,上方原来的信号和槽断开了连接,重新绑定后,再点击上方pushButton,标题修改为新标题。
  2. connect函数连接的槽函数也可以是一个lambda表达式。还可以这样使用。但是要注意lambda表达式的捕捉条件,“=”是值的方式捕捉,“&”是引用的方式,要是使用引用的方式就要注意这个变量的生命周期。
  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微yu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值