【Qt】信号和槽机制

news2024/11/18 6:30:47

目录

一、认识信号和槽

二、connect函数

三、自定义槽函数

四、自定义信号

五、带参数的信号和槽

六、信号和槽断开连接

七、信号和槽存在的意义

八、Lambda表达式定义槽函数


一、认识信号和槽

概述

在Qt中,用户和控件的每次交互过程称为一个事件。如"用户点击按钮"是一个事件,"用户关闭窗口"也是一个事件。每个事件都会发出一个信号,如用户点击按钮会发出"按钮被点击"的信号,用户关闭窗口会发出"窗口被关闭"的信号

Qt中的所有控件都具有接收信号的能力,⼀个控件还可以接收多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作。如:按钮所在的窗口接收到"按钮被点击"的信号后,会做出"关闭自己"的响应动作;再如:输入框自己接收到"输入框被点击"的信号后,会做出"显示闪烁的光标,等待用户输入数据"的响应动作。在Qt中,对信号做出的响应动作就称之为槽

信号和槽是Qt特有的消息传输机制,它能将相互独立的控件关联起来。如:"按钮"和"窗口"本身是两个独立的控件,点击"按钮"并不会对"窗口"造成任何影响。通过信号和槽机制,可以将"按钮"和"窗口"关联起来,实现"点击按钮会使窗口关闭"的效果

信号的本质

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

槽的本质

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

说明

  • 信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,即信号函数;每个槽也可以用函数表示,即槽函数
  • 信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:

信号函数用signals关键字修饰,槽函数用public slots、protected slots或者private slots修饰(使用普通成员函数的方式修饰也可)。signals和slots是Qt在C++的基础上扩展的关键字,专门用来指明信号函数和槽函数;信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)

Q_OBJECT

若一个类要使用信号和槽机制,必须在类中添加Q_OBJECT这个宏

二、connect函数

在Qt中,QObject类提供了一个静态成员函数connect(),该函数专门用来关联指定的信号函数和槽
函数。QObject是Qt内置的父类,Qt中提供的很多类都是直接或者间接继承自QObject

Qt Assistant中connect函数原型:

//旧版
connect (const QObject *sender,
    const char * signal ,
    const QObject * receiver ,
    const char * method ,
    Qt::ConnectionType type = Qt::AutoConnection )
  • sender:信号的发送者
  • signal:发送的信号(信号函数)
  • receiver:信号的接收者
  • method:接收信号的槽函数
  • type:用于指定关联方式,默认的关联方式为Qt::AutoConnection,通常不需要手动设定

但是C++不允许使用两个不同的指针类型相互赋值,使用const char*明显是不行的。因为Qt Assistant中的函数声明,是以前旧版本的Qt的connect函数的声明

在以前的版本中,给信号参数传参需要要搭配一个SIGNAL宏,给槽参数传参需要搭配一个SLOT宏。从Qt5开始,对上述写法进行了简化,给connect重载版本,第二个参数和第四个参数成了泛型函数,允许传入任意参数了

此时connect函数就带有了一定的参数检查的功能,若传入的第一个参数和第二个参数,或者第三个参数和第四个参数不匹配,代码出现编译错误。不匹配是指:2、4参数传入的函数指针,不是1、3参数的成员函数的指针

connect函数使用案例:点击按钮关闭窗口

#include "widget.h"
#include "ui_widget.h"

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, &QWidget::close);
}

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

三、自定义槽函数

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

代码编写槽函数

widget.h: 

#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);
    ~Widget();
    void HandleClicked();//槽函数声明

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);
    QPushButton* btn = new QPushButton("按钮", this);
    btn->move(200, 200);
    connect(btn, &QPushButton::clicked, this, &Widget::HandleClicked);
}

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

//槽函数定义
void Widget::HandleClicked()
{
    setWindowTitle("按钮已按下");
}

ui创建槽函数

自动生成的槽函数的名字是on_pushButton_clicked,其中on是固定的,pushButton是ui中的objectName,clicked写明了是哪种信号。ui自动生成的槽函数不需要connect函数就能在触发信号时被回调。(ui_widget.h中调用了QMetaObject::connectSlotsByName,它会触发自动连接信号槽的规则)

四、自定义信号

自定义信号很少用到。因为在GUI中,用户的操作行为是可以穷举的,Qt内置的信号已经覆盖到了大部分可能的用户操作

  • 信号是一种特殊的函数,程序员只需写出函数声明,并告诉Qt,这是一个信号即可。这个函数的定义,是Qt在编译过程中,自动生成的(无法干预)
  • 信号函数的返回值必须是void,有没有参数都可以,也支持函数重载
  • 信号可以使用emit关键字进行发射(Qt5 emit不写也行)

五、带参数的信号和槽

信号和槽也可以带参数。发射信号时,就可以给信号函数传递实参,这个参数就会被传递到对应的槽函数中。信号和槽函数的参数类型必须一致,个数可以不一致,但是信号的参数个数必须大于槽函数的参数个数(当个数不一致时,就会按顺序拿到信号的前N个参数)

一个信号可以通过connect关联多个槽函数,一个槽函数也能被多个信号关联

六、信号和槽断开连接

使用disconnect断开信号槽的连接(主动断开往往是把信号重新绑定到另一个槽函数上)

widget.cpp:

#include "widget.h"
#include "ui_widget.h"

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

    QPushButton* changeBtn = new QPushButton("修改按钮功能", this);
    changeBtn->move(200, 400);
    connect(changeBtn, &QPushButton::clicked, this, &Widget::ChangeButtonRole);
}

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

void Widget::HandleClicked_1() { setWindowTitle("修改窗口标题1"); }

void Widget::HandleClicked_2() { setWindowTitle("修改窗口标题2"); }

void Widget::ChangeButtonRole()
{
    disconnect(btn, &QPushButton::clicked, this, &Widget::HandleClicked_1);
    connect(btn, &QPushButton::clicked, this, &Widget::HandleClicked_2);
    qDebug() << "修改成功";
}

widget.h:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPushButton>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void HandleClicked_1();
    void HandleClicked_2();
    void ChangeButtonRole();

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

若这里没有disconnect,会使一个信号绑定两个槽函数,触发点击按钮,同时执行两个槽函数

七、信号和槽存在的意义

  • 解耦合:信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject类
  • 实现"多对多"的效果:一个信号可以connect到多个槽函数上,一个槽函数也可以被多个信号connect(实际开发中,这种情况极少)

缺点

与回调函数相比,信号和槽稍微慢⼀些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。通过信号调用的槽函数比直接调用的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是可以满足绝大部分场景
 

八、Lambda表达式定义槽函数

  • 注意被捕获变量的生命周期
  • 尽量传值捕获,传引用捕获可能会捕获到已经被释放的变量,造成程序崩溃
#include "widget.h"
#include "ui_widget.h"

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

    QPushButton* button = new QPushButton("点击移动", this);
    button->move(400, 800);
    connect(button, &QPushButton::clicked, this, [=](){
        qDebug() << "Lambda";
        button->move(800, 800);
    });
}

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

上述代码传值捕获没问题,传引用捕获会崩溃。原因是button是局部变量(它指向的空间位于堆区,但其本身是一个局部变量的指针),构造函数结束后button变量即被销毁,造成程序崩溃

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1467033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Spring】SpringBoot 单元测试

目 录 一.什么是单元测试&#xff1f;二.单元测试有哪些好处&#xff1f;三.Spring Boot 单元测试使用单元测试的实现步骤 一.什么是单元测试&#xff1f; 单元测试&#xff08;unit testing&#xff09;&#xff0c;是指对软件中的最小可测试单元进行检查和验证的过程就叫单元…

如何查看电脑使用记录?保障个人隐私和安全

查看电脑使用记录是了解电脑活动的一种重要方式&#xff0c;可以帮助用户追踪应用程序的使用情况、登录和关机时间、文件的访问记录等。在本文中&#xff0c;我们将介绍如何查看电脑使用记录的三个方法&#xff0c;以分步骤详细说明如何查看电脑使用记录&#xff0c;帮助用户更…

07 MyBatis之高级映射 + 懒加载(延迟加载)+缓存

1. 高级映射 例如有两张表, 分别为班级表和学生表 自然, 一个班级对应多个学生 像这种数据 , 应该如果如何映射到Java的实体类上呢? 这就是高级映射解决的问题 以班级和学生为例子 , 因为一个班级对应多个学生 , 因此学生表中必定有一个班级编号字段cid 但我们在学生的实体…

MT8791迅鲲900T联发科5G安卓核心板规格参数_MTK平台方案定制

MT8791安卓核心板是一款搭载了旗舰级配置的中端手机芯片。该核心板采用了八核CPU架构设计&#xff0c;但是升级了旗舰级的Arm Cortex-A78核心&#xff0c;两个大核主频最高可达2.4GHz。配备了Arm Mali-G68 GPU&#xff0c;通过Mali-G88的先进技术&#xff0c;图形处理性能大幅提…

PyTorch:transforms.Normalize()函数详解

PyTorch&#xff1a;transforms.Normalize()函数详解 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您的订阅和…

华为配置WDS手拉手业务示例

配置WDS手拉手业务示例 组网图形 图1 配置WDS手拉手业务示例组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 企业用户通过WLAN接入网络&#xff0c;以满足移动办公的最基本需求。但企业考虑到AP通过有线部署的成本较高&#xff0c;所以通过建立…

智慧公厕是什么?智慧公厕是构建智慧城市的环境卫生基石

随着城市化进程的不断加速&#xff0c;城市人口密度和流动性也逐渐增大&#xff0c;对城市公共设施的需求与日俱增。而在这些公共设施中&#xff0c;公厕作为城市基础设施中不可或缺的一环&#xff0c;对城市的环境卫生和市民生活质量起着举足轻重的作用。如何提高公厕的管理效…

ChatGPT plus 的平替:9个可以联网的免费AI搜索引擎

ChatGPT plus 的平替&#xff1a;9个可以联网的免费AI搜索引擎。 由于ChatGPT 训练数据截止到2021年9月&#xff0c;在该时间点之后发生的事件&#xff0c;ChatGPT均无法给出答复。所以&#xff0c;大家现在都非常期待ChatGPT能够联网&#xff0c;访问实时的信息。 ChatGPT pl…

谷歌gemma2b windows本地cpu gpu部署,pytorch框架,模型文件百度网盘下载

简介 谷歌DeepMind发布了Gemma,这是一系列灵感来自用于Gemini相同研究和技术的开放模型。开放模型适用于各种用例,这是谷歌非常明智的举措。有2B(在2T tokens上训练)和7B(在6T tokens上训练)模型,包括基础和指令调整版本。在8192个token的上下文长度上进行训练。允许商业使…

Linux之ACL权限管理

文章目录 1.ACL权限介绍二、操作步骤1. 添加测试目录、用户、组&#xff0c;并将用户添加到组2. 修改目录的所有者和所属组3. 设定权限4. 为临时用户分配权限5. 验证acl权限6. 控制组的acl权限 1.ACL权限介绍 每个项目成员有一个自己的项目目录&#xff0c;对自己的目录有完全…

Java Stream API的二度深入

Java Stream API的二度深入 前言 为什么会写这样一篇文章呢&#xff1f; 1.面试的时候&#xff0c;一位前辈对我这方面有过一次提问&#xff0c;我随口回答&#xff0c;前辈很信任我&#xff0c;以此文致敬前辈&#xff01; 2.去回顾&#xff0c;去扎实&#xff0c;对得起前辈的…

Spring及工厂模式概述

文章目录 Spring 身世什么是 Spring什么是设计模式工厂设计模式什么是工厂设计模式简单的工厂设计模式通用的工厂设计 总结 在 Spring 框架出现之前&#xff0c;Java 开发者使用的主要是传统的 Java EE&#xff08;Java Enterprise Edition&#xff09;平台。Java EE 是一套用于…

SwiftUI 支持拖放功能的集合视图(Grid)如何捕获手指按下并抬起这一操作

功能需求 假设我们开发了一款 SwiftUI 应用,其中用户可以通过拖放 Grid 中的 Cell 来完成一些操作。现在,我们希望用户在某个 Cell 被按下并随后抬起手指时得到通知,这能够实现吗? 如上图所示,我们准确地捕获到了手指在 Grid 的 Cell 上按下再抬起这一操作!那么它是如何…

算法沉淀——穷举、暴搜、深搜、回溯、剪枝综合练习四(leetcode真题剖析)

算法沉淀——穷举、暴搜、深搜、回溯、剪枝综合练习四 01.解数独02.单词搜索03.黄金矿工04.不同路径 III 01.解数独 题目链接&#xff1a;https://leetcode.cn/problems/sudoku-solver/ 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&am…

亿道丨三防平板丨加固平板丨为零售业提供四大优势

随着全球经济的快速发展&#xff0c;作为传统行业的零售业也迎来了绝佳的发展机遇&#xff0c;在互联网智能化的大环境下&#xff0c;越来越多的零售企业选择三防平板电脑作为工作中的电子设备。作为一种耐用的移动选项&#xff0c;三防平板带来的不仅仅是坚固的外壳。坚固耐用…

第十四章[面向对象]:14.9:定制类

一,__len__()方法返回长度 1,len()函数 len()函数: 功能:len() 函数返回对象(字符、列表、元组等)长度或项目个数 语法: len( s ) 参数:s : 要查询长度的对象 返回值: 返回对象长度 2,没有定义__len__()方法时,对实例应用len()函数会引发TypeError class Student: …

T-Dongle-S3开发笔记——分区表

参考&#xff1a; ESP32之 ESP-IDF 教学&#xff08;十三&#xff09;—— 分区表_esp32分区表-CSDN博客 分区表 - ESP32 - — ESP-IDF 编程指南 latest 文档 (espressif.com) 分区表是 ESP32 划分内部 flash 闪存的清单&#xff0c;它将 flash 划分为多个不同功能的区域用于…

【Java程序设计】【C00296】基于Springboot的4S车辆管理系统(有论文)

基于Springboot的4S车辆管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的4S店车辆管理系统 本系统分为销售员功能模块、管理员功能模块以及维修员功能模块。 管理员功能模块&#xff1a;管理员登录进入4S…

ubantu设置mysql开机启动

阅读本文之前请参阅----MySQL 数据库安装教程详解&#xff08;linux系统和windows系统&#xff09; 在Ubuntu系统中设置MySQL开机启动&#xff0c;通常有以下几种方法&#xff1a; 1. **使用systemctl命令**&#xff1a; Ubuntu 16.04及更高版本使用systemd作为…

【若依(ruoyi)】Java---如何在Apifox上传params参数--延伸--如何在Apifox上传Map类型参数

在使用若依开发过程中写接口的时候想在params中添加参数,但是使用params.key这种形式在后端是接收不到传过来的参数的,于是百般调研(百度),终于找到一个解决办法,就是在参数前后加上%5B和%5D,这两个参数会被编译为"["和"]",于是就对得上了,后端成功接受到参…