Qt --- 信号和信号槽

前言

Linux信号Signal,系统内部的通知机制,进程间通信方式。

信号源:谁发的信号。

信号的类型:哪种类别的信号。

信号的处理方式:注册信号处理函数,在信号被触发的时候自动调用执行。

Qt中的信号和Linux中的信号,虽然不一样,也有很多的相似之处。

Qt中谈到的信号,也是涉及到三个要素。

信号源:由哪个控件发出的信号。

信号的类型:用户进行不同的操作,就可能触发不同的信号。比如点击按钮信号。在输入框中移动光标,触发移动光标的信号,勾选一个复选框,选择一个下拉框,都会触发出不同的信号。我们写的GUI程序,就是要让用户进行操作,就是要和用户进行交互,这个过程中就需要关注,用户当前的操作具体是个什么样的操作。

信号的处理方式:槽(slot) =》就是一个函数,Qt中可以使用connect函数这样的函数,把一个信号和槽关联起来。后续只要信号触发了,Qt就会自动的执行槽函数,所谓的槽函数本质上也是一中回调函数。

一定是先把信号的处理方式准备好,再触发信号,Qt中,一定是先关联信号和槽,然后再触发这个信号,顺序不能颠倒。否则信号就不知道如何处理了。就错过信号的处理了。

一、Connect函数的用法

connect这个函数和Linux中TCPsocket中建立连接的函数,没有任何关系,只是名字恰好一样罢了。

是QObject提供的静态的成员函数。

Qt中提供的这些类,本身是存在一定的继承关系的。

QObject就是其他Qt内置类的祖宗。唉Java中也存在类似的设定。Java所有的类都是继承自Object类。

connect具体的使用方式

type参数可以先不考虑。

sender 描述了当前信号是哪个控件发出来的。

signal 描述当前信号的类型

receiver和method 信号如何处理。receiver:哪个对象负责处理。method:这个信号该如何处理。

代码:

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("按钮");
    //绑定信号和槽函数
    //最后一个槽函数是Widget类里面包含的一个槽函数
    connect(button,&QPushButton::clicked,this,&Widget::close);
    button->move(100,100);
}

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

界面上包含一个按钮,点击这个按钮就关闭窗口。所谓的信号也是QPushButton中指定的一些成员函数。

click 是一个slot函数,作用就是在调用的时候相当于点击了一下按钮。

clicked 才是要触发的点击信号。connect要求这两参数是匹配的,button的类型如果是QPushButton*,此时,第二个参数的信号必须是QPushButton内置的信号(父类的信号),不能是一个其他的类,比如QLineEdit的信号。close是QWidget内置的槽函数。Widget继承了QWidget所以Widget就可以使用close这个函数了。

两个问题:

1、咋知道QPushButton有个clicked信号?咋知道的QWidget有一个close槽?

多看文档!在翻阅文档的时候,如果在当前类中没有找到对应的线索,不妨去看看这个类的父类。

参数在QPushButton用不到。

查阅文档中的信号的时候,最重点就是关注信号的发送时机(用户进行了啥样的操作,就能够产生这个信号)

2、char*和函数指针是同一个东西吗?

所谓的指针,其实是一个统称。char*和int*和函数指针都是不同的类型! void(*)()是该函数的函数指针。bool(*)()即使是这两个函数指针的类型都是不一致的!!C++中,不允许你使用两个不同的指针类型相互赋值。

这个函数声明,是以前旧版本Qt的connect函数的声明。以前版本中传参的写法和现在其实也是有区别的。此时给信号参数传参,需要搭配一个SIGNAL宏。给槽参数传参,需要搭配一个SLOT传入的函数指针转成char*

从Qt 5开始对上述写法做出了简化,不再需要写SIGNAL和SLOT宏了。给connect提供了重载版本。重载版本中,第二个参数和第四个参数成了泛型参数,允许咱们传入任意类型的函数指针了。

第一个 Qt封装的类型萃取器,此时connect函数就带有了一定的参数检查功能。如果你传入的第一个参数和第二个参数不匹配。或者第三个参数和第四个参数不匹配。此时代码编译出错。

二、自定义信号和自定义槽

所谓的slot就是一个普通的成员函数

代码:按下按钮,修改一下窗口标题 setWindowTitle。

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("更换名字");
    //连接信号和槽函数
    connect(button,&QPushButton::clicked,this,&Widget::handler);
    button->move(100,100);
}

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

void Widget::handler()
{
    setWindowTitle("改变窗口名字");
}

所谓的自定义一个槽函数,和自定一个普通函数没有什么区别。

在以前版本的Qt中,槽函数必须放到public slots后。

Qt里广泛使用了元编程技术(基于代码生成代码),qmake构建Qt项目的时候,就会调用专门的扫描器,扫描代码中特定的关键字。基于关键字自动生成一大堆相关的代码。

第二种自定义槽的方式:

cpp 复制代码
//Qt生成的符合规则的函数名,可以被Qt自动连接信号和槽。是在ui_widget.h这个文件里处理的
void Widget::on_pushButton_clicked()
{
    setWindowTitle("修改窗口名字");
}

点击后会生成一个函数,声明也罗列好了。

现在我们就可ui编写代码了。在Qt中,除了通过connect连接信号槽意外,还可以通过函数名字的方式来自动连接。

当函数名符合上述的规则之后,Qt就能自动的把信号和槽给建立上联系!

正是在自动生成ui_widget.h中调用的。如果我们通过图形化界面创建控件,还是推荐使用这种快速的方式来连接信号槽。如果我们是通过代码的方式来创建控件,还是得手动connect,(你的代码中没有调用 connectSlotByName这样的函数)。

Qt中也允许自定义信号。

自定义槽函数,非常关键,开发大部分情况都是需要自定义槽函数的。槽函数就是用户触发某个操做之后,要进行的业务逻辑。

自定义信号比较少见,实际开发中很少会需要自定义信号。信号就对应到用户的某个操作。在GUI中用户能够进行哪些操作,是可以穷举的,Qt内置的信号,基本上已经覆盖到了。上述所有可能的用户操作。因此,使用Qt内置的信号,就足以应对大部分的开发场景了。自定义信号,本身代码比较简单的。

咱们的Widget虽然还没有定义任何信号,由于继承自QWidget和QObject,这两类里面已经提供了一些信号了,可以直接使用。所谓的Qt的信号,本质上就是一个函数,Qt5 以及更高版本中,槽函数和普通的成员函数之间,没啥差别了。

1、但是,信号则是一类非常特殊的函数,程序员只要写出函数声明,并且告诉Qt,这是一个信号即可,这个函数的定义,是Qt在编译过程中,自动生成的,自动生成的过程,程序员无法干预。信号在Qt中特殊的机制,Qt生成的信号函数的实现,要配合Qt框架做很多既定的操作!!

2、作为信号函数,这个函数的返回值,必须是void。有没有参数都可以,甚至也可以支持重载。

qmake的时候,调用一些代码的分析生成工具。扫描到类中包含signals这个关键字的时候,此时就会自动的把下面的函数声明认为是信号,并且给这些信号函数自动的生成函数定义。

建立连接,不代表信号发出来了,如何才能触发自定义的信号呢?

Qt内置的信号,都不需要咱们手动通过代码来触发,用户在GUI,进行某些操作,就会自动触发对应信号(发射信号的代码已经内置到Qt框架中了)。emit发射信号。emit mySignal(); emit是Qt扩展出来的关键字。其实在Qt 5中emit现在啥都没做。真正的操作都包含在mySignal内部生成的函数定义了。即使不写emit,信号也能发出去!即使如此,实际开发中,还是建议把emit都加上,加上代码可读性更高,更明显的表示出,这里是发射自定义信号。

代码:

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("按钮");
    connect(this,&Widget::Mysignal,this,&Widget::MysignalHand);
    
}

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

void Widget::MysignalHand()
{
    setWindowTitle("自定义信号");
}

emit发送信号

信号和槽也可以带参数。当信号带有参数的时候,槽的参数必须和信号的参数一致。此时发射信号的时候,就可以给信号函数传递实参,与之对应的这个参数就会传递到对应的槽函数中。一致主要是要求类型一致,个数如果不一致也可以,不一致的时候,要求信号的参数的个数必须比槽的参数要更多。

传参可以起到复用代码的效果。有多个逻辑,逻辑上整体基本一致,但是涉及到的数据不同,就可以通过函数 - 参数来复用代码,并且在不同的场景中传入不同的参数即可。

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button1 = new QPushButton(this);
    button1->setText("按钮1");
    button1->move(100,100);
    QPushButton* button2 = new QPushButton(this);
    button2->setText("按钮2");
    button2->move(200,200);
    connect(button1,&QPushButton::clicked,this,&Widget::handler1);
    connect(button2,&QPushButton::clicked,this,&Widget::handler2);
}

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

void Widget::MysignalHand(const QString& text)
{
    setWindowTitle(text);
}

void Widget::handler1()
{
    MysignalHand("传递参数1");
}

void Widget::handler2()
{
    MysignalHand("传递参数2");
}

通过这一套信号槽,搭配不同的参数,就可以起到设置不同标题的效果。Qt中很多内置的信号,也是带有参数的。这些参数不是自己传递的。clicked信号就带有一个参数。

信号函数的参数多于槽函数参数的个数,此时可以正常使用。

信号函数的参数个数,少于槽函数的参数个数,此时代码无法编译通过。一个槽函数,有可能绑定多个信号。如果我们严格要求个数一致,就意味着信号绑定到槽的要求就变高了。换而言之,当下这样的规则,就允许信号和槽之间的绑定更灵活了。更多的信号可以绑定到这个槽函数上了。要求信号给槽的参数,可以富裕。但是不能少。

带有参数的信号,要求信号的参数和槽的参数要一致。类型、个数要满足要求(信号的参数个数要多于槽的参数个数,也可以相等)。

Qt中如果要让某个类能够使用信号槽,(可以在类中定义信号和槽函数)。则必须要在类的最开始的地方,写下Q_OGJECT宏。会进行宏展开,最终展开的效果会得到一系列很复杂的代码。这些代码就涉及到了Qt实现的内部原理了,这里就不深入研究了,如果不加这个宏,这个类就会编译报错。

三、信号和槽的意义

所谓的信号槽,终究要解决的问题,就是响应用户的操作。信号槽,其实在GUI开发各种框架中,是一个比较有特色的存在。这是一个高情商的说法,其他的GUI开发框架,搞的方式都要更简洁一些。网页开发(js + dom api)网页开发中响应用户的操作,主要就是挂回调函数。不要搞一个单独的connect完成上述的信号槽连接。处理函数u,就像控件的一个属性/成员一样。大部分的GUI开发框架都是这么搞的。

Qt信号槽,connect这个机制,设想很美好的。

1)解耦合,把触发用户操作的控件和处理对应用户的操作逻辑解耦合。

2)多对多的效果:一个信号可以connect到多个槽函数上。一个槽函数,也可以被多个信号connect。前端开发只能一对一。一个事件只能对应一个处理函数。Qt谈到的一个多对多和我们的数据库使用的多对多是相似的。数据库中是通过关联表实现的。Qt也是这样的。

综上所述,Qt引入信号和槽机制,最本质的目的。就是为了能够让信号和槽之间按照多对多的方式来进行关联。其他的GUI框架往往是不具备这样的特性的。实际上,随着程序开发这个事情,大家的经验越来越多。其实在GUI开发的过程中,多对多这个事情,其实是个伪需求,实际开发很少会用到绝大部分情况,一对一就够用了。新出现的一些图形化开发框架,很少有在继续支持这种多对多的了。Qt有很多的优点,值得我们区深究。

四、信号和槽断开连接

1、使用disconnect来断开信号槽的连接。这个东西用的比较少,大部分的情况下,把信号和槽连接了以后,就不必管了。

代码:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->pushButton_2,&QPushButton::clicked,this,&Widget::Handler1);
}

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

void Widget::Handler1()
{
    setWindowTitle("按钮1");
}

void Widget::Handler2()
{
    setWindowTitle("改变成功");
}



void Widget::on_pushButton_clicked()
{
    //解除按钮1原有的信号和槽函数的连接
    disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::Handler1);
    //重新连接信号和槽函数的连接
    connect(ui->pushButton_2,&QPushButton::clicked,this,&Widget::Handler2);
}

切换原来信号绑定的槽函数。

2、定义槽函数的时候,也是可以使用lambda表达式的!很多编程语言都支持,语法糖。本质就是一个匿名函数,主要应用在回调函数场景中。lambda表达式是一个回调函数。这个函数无法获取到外面的变量的。lambda为了解决上述问题,引入了变量捕获。写作[=]把上层的变量都给捕获。后续对应的槽函数比较简单,而且是一次性使用的,就经常会写作这种lambda的形式。另外也要确认捕获的lambda内部的变量是有意义的。无论何时用户点击了按钮,捕获到的变量都能正确使用。

代码:

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("按钮lambda");
    connect(button,&QPushButton::clicked,this,[=](){
        button->move(100,200);
    });
}

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

小结:

1、信号槽是啥 信号源,信号的类型,信号的处理方式。

2、信号槽的使用 connect。

3、如何查阅文档,一个控件,内置了哪些信号,信号都是何时触发。一个控件内置了哪些槽,槽都是什么作用。

4、自定义槽函数。还可以让Qt Creator自动生成。

5、自定义信号 信号本质就是一个成员函数,函数的定义是Qt自己生成的,咱们只需要写函数声明。

6、信号和槽还可以带有参数,类型匹配,信号的参数要多于槽的参数。

7、信号槽存在的意义 解耦合 多对多的效果。

8、disconnect使用方式。

9、lambda表达式,简化槽函数的定义。

相关推荐
傻啦嘿哟14 分钟前
如何使用 Python 开发一个简单的文本数据转换为 Excel 工具
开发语言·python·excel
大数据编程之光19 分钟前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
初九之潜龙勿用19 分钟前
C#校验画布签名图片是否为空白
开发语言·ui·c#·.net
Dola_Pan36 分钟前
C语言:数组转换指针的时机
c语言·开发语言·算法
ExiFengs36 分钟前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring
paj12345678938 分钟前
JDK1.8新增特性
java·开发语言
IT古董1 小时前
【人工智能】Python在机器学习与人工智能中的应用
开发语言·人工智能·python·机器学习
繁依Fanyi1 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
湫ccc1 小时前
《Python基础》之pip换国内镜像源
开发语言·python·pip
fhvyxyci1 小时前
【C++之STL】摸清 string 的模拟实现(下)
开发语言·c++·string