QT系列(六)--信号和槽

系列文章目录



前言

信号的三要素

  1. 信号源:谁发的信号
  2. 信号的类型:哪种类别
  3. 信号的处理方式:注册信号处理函数,在信号被触发的时候自动调用执行

在QT中

  1. 信号源:哪个控件发出的信号
  2. 信号的类型:用户进行不同的操作,可能触发不同的信号
    比如(1)点击按钮,触发点击信号(2) 输入框中移动光标:触发移动光标的信号
    (3) 勾选复选框(4) 选择下拉框

GUI程序实际上就是让用户操作,程序和用户交互,整个过程需要关注用户当前的操作具体是什么。

信号的处理方式被称作槽函数。

QT中使用connect这样的函数,信号和槽关联起来,信号触发就会自动执行槽函数
槽函数就是回调函数,callback

提前把信号处理方式准备好了再触发信号

接下来我们讨论connect函数的相关问题


一、connect函数

该函数和linux中TCP 的socket没有直接关系,是QObject提供的静态成员函数

QT中提供这些类,本身是存在一定的继承关系的。 以下是connect函数的定义。

connect(const QObject*sender,//信号源:哪个控件发出的信号
        const char* signal,  //信号类型
        const QObject* receiver,//如何处理 哪个对象负责处理
        const char*method,//如何处理:整个对象该怎么处理
        Qt::ConnectionType type = Qt::AutoConnection)//暂时不考虑,很少使用

二、例:点击界面按钮关闭窗口

按住ctrl + 鼠标左键可以跳转到定义
alt + ←可以跳转回去
connect(button,&QPushButton::clicked,this&Widget::close);

上述connect函数其中的信号也是成员函数
click是slot函数(槽函数),调用的时候相当于点击了一下按钮
clicked,点完了之后触发点击信号

图标带锯齿是槽函数,WiFi图标的是信号

  • 在connect函数中connect(button,&QPushButton::clicked)前两个参数必须是匹配的
  • button的类型是QPushButton*,第二个参数必须是QPushButton内置的信号(父类的信号),不能是其他的类
  • connect(button,&QPushButton::clicked,this)this是指父类为执行对象(widget),
    connect(button,&QPushButton::clicked,this,&Widget::close)
    close是QWidget内置的槽函数,Widget。
  • close槽函数功能已经内部实现好了,具体作用就是关闭窗口,点击后就会关闭。

两个问题

  1. 如何知道QPushButton有clicked信号
  2. QWidget有一个close槽
    文档!
    如果翻阅文档的时候,没有找到,可以看看父类
    在这里插入图片描述
    QT中有很多按钮,QAbstractButton中式各种按钮的共性内容
    在这里插入图片描述
    查阅文档的时候,关注信号的发送时机(用户进行什么样的操作产生这个信号)
    2,4填写的是函数指针,

为什么函数定义和实现不一样?

connect(const QObject*sender,//信号源:哪个控件发出的信号
        const char* signal,  //信号类型
        const QObject* receiver,//如何处理 哪个对象负责处理
        const char*method,//如何处理:整个对象该怎么处理
        Qt::ConnectionType type = Qt::AutoConnection)//暂时不考虑,很少使用

connect(button,&QPushButton::clicked,this,&Widget::close)
观察函数定义,在2,4位置定义是char*,而我们填入的是void(*)(),和bool(*)(),

  • C++中是不允许不同的指针类相互赋值(函数传参),这是因为给出的是旧版本的。此时给信号参数传参要搭配signal宏,槽传参搭配slot宏,将传入的函数指针转成char*。
    connect(button,SIGNAL(&QPushButton::clicked),this,SLOT(&Widget::close))
  • QT5开始,对上述写法做出简化,不需要写两个宏,给connect提供重载版本,重载版本中,2,4参数成了泛型参数允许传入任意类型的函数指针了

定义如下:

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)

现在有了1->Func1,2->Func2类型萃取器
此时的connect带有了参数检查功能,如果第一,第二个参数不匹配,或者3,4个参数不匹配,代码就会编译出错。

三、自定义槽函数

自定义信号 -> QPushButton
自定义槽 -> 普通的成员函数(没区别)

按钮修改标题完整代码

#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 ->move(100,100);
    connect(button,&QPushButton::clicked,this,&Widget::handleClicked);
}

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

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

以前必须将槽函数放到public slots:QT自己的扩展关键字(不是C++的标准语法)
QT广泛使用元编程技术,调用专门的扫描器,扫描代码中特定的关键字,基于关键字生成代码,因此槽函数设置为public即可。
(不能理解的可以看看c++的相关知识)

图形化的方式

图形化界面设置按钮,右键,转到槽
在这里插入图片描述

  • QPushButton提供的所有信号都包含在这里(还包含了QPushButton父类的信号)
  • 选择clicked就会看到生成好的函数(声明和定义),可以直接编写需要的代码。
  • 该函数不用connect!!!
  • 因为在QT中,除了通过connect连接信号槽之外,还可以通过函数名字的方式自动连接
void Widget::on_PushButton_clicked()//该名字包含了信息

当命名规则符合了上述规则,QT也能自动把信号和槽连接起来。

  • 因为QT调用QMwtaObject::connectSlotsByName:调用该函数就会触发上述自动连接信号槽的规则,在自动生成的ui_Widget.h中调用

用哪种方式呢?

图形化界面创建控件,推荐快速方式
如果代码方式创建,手动connect,(自己的代码没有调用自动命名规则)

四、自定义信号

  • 自定义槽函数是一件很关键的事情。(用户触发之后的业务逻辑)
  • 自定义信号很少见,实际开发中很少,因为用户的操作可以穷举,内置信号基本覆盖到上述所有可能的用户操作。因此自定义信号本身代码比较简单。
    自己的Widget虽然没有定义任何信号,但是由于继承自QWidget和Qobject,这俩类里面已经提供了一些信号了,可以直接使用
  • QT信号本身就是一个函数,QT高版本中,槽函数和普通成员函数没区别,但是信号很特殊,
  • 只要写出函数声明并且告诉QT是一个信号即可,函数定义在编译过程自动生成(该过程程序员无法干预)。

信号在QT中式特殊的机制,QT生成的信号函数的实现,要配合QT框架做很多既定的操作

  • 作为信号函数,函数返回值必须是void,有没有参数都可以,甚至支持重载

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

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

如何触发自定义的信号呢?

  • QT内置信号,都不需要手动通过代码来触发,用户在GUI进行某些操作,会自动触发信号(发射信号的代码已经内置到QT框架中了)
    但是自己的信号,QT提供了emit mySignal();

完整代码如下:

#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);//建立连接不代表信号发出
    emit mySignal();
}

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

void Widget::handleMySignal()//注意别忘了写声明
{
    this->setWindowTitle("处理自定义信号");//设置窗口标题
}

发送的自定义信号,可以在任意合适的代码中,不一定在构造函数里面也可以自定义点击的时候发送信号。
比如可以在点击按钮的时候发送信号

void Widget::on_pushButton_clicked()
{
    emit mySignal();
}

其实在QT5中emit啥都没做,真正操作都包含在mySignal内部生成的函数定义中了,即使不写emit也能把信号发射出去,写了之后代码可读性更高

五、带参数的信号和槽

  当信号带参数,槽的参数必须和信号一致,发射信号的时候就可以给信号函数传递实参,与之对应的参数就会被传递到对应的槽函数中(此时起到了传递参数的作用)

signals:
    void mySignal(const QString&);//自动生成定义
public:
    void handleMySignal(const QString&);
  • 参数一致要求的主要是类型,个数不一致也可以,不一致的时候要求信号的参数的个数必须比槽的参数个数更多。
  • 形参名字可以不写,只写类型(C++基础语法中的)
void Widget::handleMySignal(const QString& text)
{
    this->setWindowTitle(text);//槽
}


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

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

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

  QT很多内置信号也带有参数,但不是自己传递,比如clicked,有两种版本,bool表示当前按钮是否处于选中状态,对于QCheckBox很有用

参数个数不一致的情况

  • 在上述基础上给信号函数增加参量,不影响实现(信号2,槽1)
  • 信号个数少于槽函数参数个数(信号1槽2),会报错

一个槽函数有可能会绑定多个信号,如果严格的要求个数一致,就意味着信号绑定到槽的要求变高了。当前情况下绑定灵活,更多信号可以绑定到槽函数,个数不一致,槽函数会按照参数顺序拿到钱N个参数,至少要确保槽函数到每个参数都有值。(不能少)

参数类型不一致

信号和槽参数类型不一致会导致无法编译

使用信号槽的前提

  QT中如果某个类能够使用信号槽,必须在最开始写下Q_OBJECT的宏,该宏能展开成很多额外的代码。之后的宏还能进一步展开,最终会得到很多复杂的代码,如果不加宏会编译出错
在这里插入图片描述

信号和槽存在的意义

  • 信号槽解决的是响应用户的操作

  信号槽在GUI开发的各种框架中,比较有特色(其他GUI更简洁,比如网页开发js+dom api,主要是挂回调函数,处理函数就像控件的一个属性/成员一样~~,大部分GUI都这样)
QT的connect这个机制设想是

  1. 解耦合,触发用户操作的控件处理对应用户的操作逻辑 解耦合
  2. 多对多效果(和数据库非常相似) 学生表 课程表 学生-课程表

以下类似于数据库中关联表的

 connect(this,&Widget::mySignal1,this,&Widget::mySlot1);
 connect(this,&Widget::mySignal1,this,&Widget::mySlot2);
 connect(this,&Widget::mySignal1,this,&Widget::mySlot3);

其实在GUI开发中,多对多是一个伪需求,绝大多数一对一就够用了,新出现的框架,很少再支持多对多的了。

六、信号和槽断开连接

  1. 使用disconnect来断开信号槽的连接(使用很少,大多数连上了就不用管了),主动断开是把信号绑定在另一个槽函数上
void Widget::handleClick()
{
    this->setWindowTitle("修改窗口的标题1");
}

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


void Widget::on_pushButton_2_clicked()
{
    //1.先断开原来的信号槽
    disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick);
    //2.重新绑定
    connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick2);
}

如果没有disconnect,就会出现一个信号绑定了两个槽函数,两个槽函数都会执行。
在这里插入图片描述

  • 上述结果为点击顺序按钮1->按钮2(切换槽函数)->按钮三的打印结果。
  • 也就是说,不disconnect,切换槽函数的作用变成了一个信号对应两个信号槽,handleclick1和handleClick2同时执行

七、 使用lambda表达式定义槽函数

lamda本质上是一个回调函数

 QPushButton* button = new QPushButton(this);
    button->setText("按钮");
    button->move(200,200);
    connect(button,&QPushButton::clicked,this,[](){
        qDebug()<<"lanmda 被执行了";
    });

这样简化了信号槽的写法。但是当前参数什么也没干,如果像点击按钮时移动按钮位置,button找不到定义怎么办?

  • lanbda表达式本质上是回调函数,这个函数无法直接获取到上层作用域中的变量。解决问题引入变量捕获的语法,通过变量捕获,获取到外层作用域中的变量
connect(button,&QPushButton::clicked,this,[button,this](){//[=]
        qDebug()<<"lanmda 被执行了";
        button->move(300,300);
        this->move(100,100);
    });
  • 如果当前lanmda里面想使用更多的外层变量,写作[=],这个写法就是把上层作用域中所有变量名捕获进来。
  • 槽函数简单而且一次性使用,就会写作lanmbda的形式,回调函数执行时机是不确定的,无论何时用户点击按钮,捕获到的变量都能正确使用
  • button是new出来的变量,生命周期挂到整个窗口(对象树上,窗口关闭才会释放),这个东西可以随时使用。(53)
  • lanmda除了值的方式捕获[=],还有引用的方式[&],(QT中很少写,捕获的是控件指针,指针变量按值还是引用来传递都无所谓,&方式还要关注本身的周期。因此按值捕获就够了!)

该语法为C++ 11中引入的,QT5以及更高版本,默认C++11编译的,QT4或者更老的版本,需要手动添加C++11的编译选项

CONFIG +=c++11  //入乡随俗

总结

  1. 信号槽。信号源,信号的类型,信号处理方式
  2. 信号槽的使用。新版本
  3. 如何查阅文档(控件内置信号,何时触发,槽的作用【找父类!】)
  4. 自定义槽函数(普通成员函数)图形化界面也可以自动生成,函数名特定规则完成
  5. 自定义信号 本质是成员函数(定义自动生成,只用写声明)signals自定义关键字中emit完成发射
  6. 信号和槽带参数,参数一致(类型,信号个数可以多)
  7. 信号槽意义,解耦合,多对多

高内聚,低耦合
耦合:两者之间影响很大
内聚:某个功能点集中放在一起

  1. lambda表达式,简化槽函数定义
  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
QtTCP通信中,可以使用信号机制来处理各种事件。下面是一些常用的TCP通信的信号: 1. QAbstractSocket类提供了关于TCP连接的信号,比如connected()、disconnected()、error()等。可以通过连接这些信号来处理连接建立、断开以及错误处理等事件。 2. QTcpServer类用于实现TCP服务器端,它有一个新的连接信号newConnection(),当有新的客户端连接时会触发该信号。可以通过连接这个信号来接受新的客户端连接,并创建对应的QTcpSocket对象来进行通信。 3. QTcpSocket类用于实现TCP客户端和服务器端的通信。它提供了一系列与TCP通信相关的信号,比如readyRead()、bytesWritten()、disconnected()等。readyRead()信号在有数据可读时触发,可以通过连接这个信号来读取数据。bytesWritten()信号在数据发送完成后触发,可以通过连接这个信号来判断数据是否发送成功。disconnected()信号在连接断开时触发,可以通过连接这个信号来处理断开连接的事件。 4. QTcpSocket类还提供了一些用于发送和接收数据的函数,比如write()用于发送数据,readAll()用于读取所有可用数据等。可以通过连接这些函数的调用和相应的信号来实现数据的发送和接收。 通过使用信号机制,可以很方便地处理TCP通信中的各种事件,使代码结构清晰易于维护。希望以上内容对你有所帮助!如果有更多问题,请继续提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值