细谈QT信号与槽机制

转自个人博客

信号与槽是我个人认为QT中最牛的机制之一,最近没有其他的内容可写,今天就来细细总结一下这个信号与槽机制。

1. 信号与槽机制概述

信号与槽机制可以理解为QT中的一种通信手段,在运行相关代码前,分别声明信号和槽,再利用connect()方法将信号和对应的槽连接起来,之后再需要的地方使用emit触发信号,那么就可以让槽响应。

其中,槽可以是特定的槽函数,也可以是其他各种普通函数;信号是一种特定对象,其结构类似函数,也可以像函数那样带有参数,将值通过参数传递槽函数,便于槽函数处理变量,如果使用参数,则信号和槽的参数要保持一致。

示例:

class MyClass : public QObject 
{
protected:MyClass(QObject *parent = nullptr);~MyClass();void myFunc();signals:void dataArrived(string data);slots:void onDataArrived(string data);
}///MyClass::MyClass(QObject *parent = nullptr)
{// 在使用信号与槽函数之前连接信号和槽connect(this, &MyClass::dataArrived, this, &myClass::onDataArrived);
}
MyClass::~MyClass()
{// 在不用的时候释放连接disconnect(this, &MyClass::dataArrived, this, &myClass::onDataArrived);
}void MyClass::onDataArrived(string data)
{// 处理data或其他操作
}void MyClass::myFunc()
{....// 在需要的地方触发信号,执行onDataArrived槽函数,并传递数据emit dataArrived(data);
}

这个示例中,只在一个类中使用信号和槽传递数据,其效果与直接调用函数无异,但信号与槽的功能远超如此。

可以使用信号与槽实现跨类通信、跨线程通信,保证线程不会阻塞,实现异步处理。一个槽可以同时与多个信号连接,便于代码中应对不同情况,不用命名各种函数。而且信号与槽可以任意连接、解耦,也可以实现在不同的地方将一个信号与不同槽函数连接,轻松改写结构,而不会影响代码其他部分。

2. QT中信号与槽

2.1 基础用法

我这里只提QT5中的用法,旧版本的用法几乎不再使用。

2.1.1 信号signal
  1. 声明的格式

    需要在头文件中使用signal关键字修饰单一对象,以指定信号对象,或者使用signals:来批量修饰对象,以批量指定信号对象。另外,Q_SIGNAL等同于signalQ_SIGNALS:等同于signals:

    信号本体格式等同于函数,但不需要再在CPP文件中定义“函数体”,返回值为void。也可声明参数,以传递数据,但参数格式与槽保持一致,至少参数数目一样多,并且参数之间可以隐式转换,好比函数的调用。

    示例:

    class MyClass : public QObject 
    {// 修饰单一信号对象// Q_SIGNAL等同signalQ_SIGNAL void fileDeleted(); // 批量修饰信号对象
    // signals等同Q_SIGNALS
    signals: // 可声明参数void fileCreated(string filePath);void fileModifed();
    }
    
  2. 使用

    先使用connect()方法连接信号与槽,在需要的地方,使用emit触发信号,如有参数,就需要保证格式的正确。

    emit fileDeleted();
    Q_EMIT fileCreated(path);
    
2.1.2 槽slot
  1. 声明的格式

    可选的在头文件中使用slot关键字修饰单一对象,以指定槽函数,或者使用slots:来批量修饰对象,以批量指定槽函数。另外,Q_SLOT等同于slotQ_SLOTS:等同于slots:

    槽函数的本身就是函数,即使用函数格式,需要在CPP文件中定义函数体,任意定义返回值以应对其他需要调用此函数的地方。不同于信号必须使用signal关键字告诉编译器这是信号对象,槽函数也可以直接使用普通函数作为连接对象。

    示例:

    class MyClass : public QObject 
    {
    // 普通函数可直接被当作槽函数用connect方法与信号连接
    // 可以有返回值
    bool onFileModifed();public: // 需要修饰访问权限// Q_SLOT等同slotQ_SLOT void onFileDeleted();// 需要修饰访问权限, slots等同Q_SLOTS
    public slots:// 可声明参数void onFileCreated(string filePath);
    }
    
  2. 使用

    作为一个函数,需要在CPP文件中定义函数体,在需要的时候使用connect()方法连接信号与槽,以便之后被信号调用。

    bool MyClass::onFileModifed()
    {/// 函数体...
    }void MyClass::onFileCreated(string filePath)
    {/// 函数体...   
    }
    
2.1.3 connect()方法

1. QT5中的connect()方法:

使用connect()方法将信号和槽建立连接,以便信号触发时,执行指定槽函数。

connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction slot, Qt::ConnectionType type = Qt::AutoConnection)

  • 参数一sender:信号发出的对象
  • 参数二signal:信号所在类名和信号名称,格式为**&类名::信号名**
  • 参数三receiver:信号接收的对象,槽函数所在的对象
  • 参数四slot:槽函数所在类名和槽函数名称,格式为**&类名::槽函数名**
  • 参数五(在2.2中详细介绍)

示例:

QLabel *label = new QLabel;
QLineEdit *lineEdit = new QLineEdit;
QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);

2. disconnect()方法:

使用disconnect()方法释放信号和槽连接,便于之后此信号和不同槽函数建立连接,一般不使用此信号和槽时就要记得释放连接。

disconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction slot)

格式与connect时保持一致,但disconnect()方法没有第五参数,即便connect()时指定了第五参数,disconnect()时直接省略。

示例:

// 建立连接
QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);
// 释放此连接
QObject::disconnect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);

3. 使用Lambda表达式作为槽函数:

当然,前面又说到槽可以为其他函数,也就可以使用Lambda表达式来作为槽函数,使用Lambda表达式可以简化槽函数的声明,也可以通过Lambda表达式随着信号调用参数格式与信号不一致的函数,或增加更多的使用条件。

关于C++ Lambda表达式可见我另一篇文章:C++ Lambda表达式

示例:

class MyClass : public QObject 
{
protected:MyClass(QObject *parent = nullptr);void myFunc(string data, int size);signals:void dataArrived(string data);
}///
MyClass::MyClass(QObject *parent = nullptr)
{connect(this, &MyClass::dataArrived, this, [&](){myFunc(data, data.lenght());/// 或进行其他处理});
}

2.2 connect()第五参数与多线程

connnect()方法通过使用第五个参数来设定槽函数的调用关系,此参数有五个固定的枚举值:

  1. Qt::AutoConnection:自动连接,默认模式,根据信号和槽调用的线程自动进行选择。如果信号和槽在同一线程中,则使用Qt::DirectConnection类型连接;如果在不同线程,则使用Qt::QueuedConnection连接。
  2. Qt::DirectConnection:直接连接。当信号和槽运行在同一线程中时使用,其效果类似函数调用,直接在信号发送的位置调用槽函数。
  3. Qt::QueuedConnection:队列连接。当信号和槽运行在不同线程中时使用,信号发出时,不会立即调用槽,而是将其放入接收者所在线程的事件队列中,等到接收者当前函数执行完,进入事件循环之后,槽函数才会被调用。
  4. Qt::BlockingQueuedConnection:阻塞队列连接。当信号和槽运行在不同线程中时使用,类似于 QueuedConnection,但在调用槽时会阻塞信号的发送者,直到槽执行完成。所以如果信号和槽在同一线程时,就会造成死锁,适用于需要确保槽完成执行再继续其他操作的场景。
  5. Qt::UniqueConnection:唯一连接,可以与上述四个通过或字符|结合使用。此枚举值与上述四个不同,其不指定槽函数的调用方式,而是确保该连接在相同的信号和槽组合中是唯一的。如果尝试将同一信号和槽再次连接,连接将不会生效。这可以防止重复连接,避免一次触发信号多次运行槽函数。

示例:

QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::QueuedConnection | Qt::UniqueConnection);

3. 于QML中使用信号与槽

QML中的信号与槽机制与C++中的使用方法类似。但QML中定义的任意属性变量都有自己的信号,无需用户自己定义,几乎所有组件都是如此,一般使用时,更多情况会使用QML为这些对象设定的信号。当然也可以自定义信号和槽函数以及相互的连接。

3.1 监控QML默认定义的信号

QML中为组件默认属性或自定义属性都默认设置了信号,定义对应槽函数就可以在信号触发后调用。槽函数的格式为**on+信号名(首字母大写)**,可由此自定义触发信号后的执行代码。

1. 组件属性

大多数QML组件都有内置信号,这些信号可以直接连接到槽函数来响应特定事件。

一般的默认属性都有自己的指定触发方式,比如Button组件通过点击触发clicked()信号,当然也可以直接调用clicked()手动触发信号。

示例:

// Button组件的clicked信号在用户点击按钮时触发
// 通过onClicked自定义代码,在信号触发时输出指定内容
Button {  onClicked: {  console.log("Button was clicked!");  }  
}  

2. 自定义属性变量

QML中的自定义属性变量也会自动创建变化信号,当属性值发生变化时会触发相应的信号。

对于QML中使用 property 声明的自定义属性,首字母必须小写,其发生改变时触发信号的槽函数格式为 on+属性名(首字母大写)+Changed

示例:

property int num: 0  // 自动创建的信号,当num发生变化时触发  
onNumChanged: {  console.log("num changed to:", counter);  
}    

3.2 自定义信号与槽

1. 自定义信号signal

QML中也可以直接使用 signal 关键字定义专门的信号属性,可以带有参数,格式参考下方示例,同样首字母小写。

槽函数格式即为**on+信号名(首字母大写)**。

触发信号直接使用信号名调用信号即可,触发信号后就直接执行槽函数。

注意: signal 关键字定义信号只能在QML文件的根组件使用

示例:

ColumeLayout { // 根组件signal mySignal(string data)  //信号触发时调用,同槽函数onMySignal: {  console.log(data)  }Button: {  text: "Emit mySignal"  onClicked: {// 触发信号mySignal("Hello World!"); }}  
}  

2. Connections{}

Connections 组件用于处理信号的连接。重要的是它可以在不同作用域之间连接信号和槽,适合在运行时动态处理信号,特别是在定义和使用信号的组件不在同一上下文时。

格式上,使用target属性指定信号发出对象,然后就可以监听发出对象内部的信号,信号对应的槽函数必须用function修饰

注意:

  1. 因为是跨作用域连接信号,对方根组件的信号自然是能监听,但子组件的信号就监听不了了。就好比可以调用根组件的属性变量,调用不了子组件的属性变量
  2. 这个方法同样可以运用于C++注册给QML的类,监听C++中暴露的方法。

示例1:

// MyButton.qml
Button {property string myText: ''signal doubleClicked()onDoubleClicked: {console.log(myText + "double clicked")}
}// MyItem.qml
ColumeLayout { // 根组件MyButton {id: myButton}Connections{target: myButton // 信号发出对象// 注意变量的信号名格式,注意function修饰function onTextChanged(){myFunc() // 执行本作用域的函数}function onDoubleClicked(){// 在本作用域再定义一个此信号的槽函数}}function myFunc(){// ...}
}

示例2:

Connections{target: qwidget // C++注册给QML的名称// 监听C++中暴露给QML的变量的变化function onWidthChanged(){// ...}function onHeightChanged(){// ...}
}

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

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

相关文章

Docker Swarm 与 Kubernetes 在集群管理上的主要区别

Docker Swarm 和 Kubernetes 是两种流行的容器编排工具,它们都可以用于部署、管理和扩展容器化应用,但在集群管理方面有明显的差异。 下面从多个维度对比它们在集群管理上的主要区别: ✅ 一、总体定位 项目Docker SwarmKubernetes官方支持D…

【StarRocks系列】查询优化

步骤参考官网 分析查询 | StarRocks StarRocks-Profile分析及优化指南 StarRocks-Profile分析及优化指南 - 经验教程 - StarRocks中文社区论坛

软测八股--测试理论 1 测试基础

软件测试? 发现程序中的侧屋执行程序工程 目的:不仅是找出错误,还要分析错误产生原因和错误分布。检查开发如阿健过程出现的bug,使开发人员及时修改。测试只能说明软件中存在错误 目标:尽可能发现多的错误。一个好的…

mfc与vs成功在xp系统所需做的修改

目录 前言一、MFC程序 inet_pton 、CT2A 未声明问题1)问题1:inet_pton :undeclared identifier - inet_pton未声明2)问题1:CT2A :undeclared identifier - CT2A未声明 二、VS程序 使用事件、委托问题1&…

SpringMVC系列(三)(请求处理的十个实验(上))

0 引言 作者正在学习SpringMVC相关内容,学到了一些知识,希望分享给需要短时间想要了解SpringMVC的读者朋友们,想用通俗的语言讲述其中的知识,希望与诸位共勉,共同进步! 本系列会持续更新!&…

Python案例练习:函数专题

用函数重新设计文章单词出现次数程序 composition This is my family. We have a father, a mother and two brothers. My father is a doctor. He works in a hospital. My mother is a teacher. She teaches English in a school. My older brother is a student. He stu…

数据驱动 AI 时代:数据库行业的技术跃迁与生态重构

在数据驱动的 AI 战场,真正的决胜武器不是复杂精妙的算法模型,而是深埋在企业核心系统中的高维数据网络(图)。 时至今日,市场对AI的风向正从“狂飙突进”转向“精耕细作”,就在上周(米国时间6月…

码分复用通俗理解

一、码分复用(CDMA)的通俗理解 码分复用(Code Division Multiple Access,CDMA)是一种让多个用户在同一时间、同一频段上同时传输数据的技术。它的核心思想是:给每个用户分配一个唯一的“编码钥匙”&#x…

MAC、IP地址、TCP、UDP、SSL、OSI模型

目录 一 概要 二 MAC和IP地址 (1)什么是 MAC 地址? (2)什么是 IP 地址? (3)MAC 地址 vs IP 地址对比表 (4)总结一下 二 TCP、UDP、SSL详细介绍 &#…

弱口令介绍及破解方式笔记

一、弱口令的定义与危害 弱口令是指容易被猜测或破解的密码,通常包含简单数字组合(如“123456”)、键盘连续字符(如“qwerty”)、个人信息(如生日、姓名)或常见词汇(如“password”…

Kotlin 中ArrayList、listOf、arrayListOf 和 mutableListOf区别

在 Kotlin 中&#xff0c;ArrayList、listOf、arrayListOf 和 mutableListOf 是常用的集合创建方式&#xff0c;但它们在类型、可变性和使用场景上有明显区别。以下是核心对比&#xff1a; 一、基本定义与类型 创建方式类型可变性底层实现ArrayList<T>()ArrayList<T…

C盘清理技巧分享:释放宝贵空间,提升电脑性能

Hi&#xff0c;我是布兰妮甜 &#xff01;随着电脑使用时间的增长&#xff0c;C盘空间逐渐被各种文件占据&#xff0c;导致系统运行缓慢、程序响应迟钝。对于许多用户来说&#xff0c;C盘空间不足是一个常见且令人头疼的问题。本文将分享一系列实用的C盘清理技巧&#xff0c;帮…

【 感知集群】大规模分布式基础设施的AI赋能蓝图

第一部分&#xff1a;AIOps基础——从被动响应到预测性运维 在本报告的初始部分&#xff0c;我们将为管理一个复杂的大规模集群建立关键的基线。我们认为&#xff0c;在追求高级优化或未来主义架构之前&#xff0c;一个强大、由人工智能驱动的运维基础是不可或缺的。本部分将详…

《R循环:深度解析与高效使用技巧》

《R循环:深度解析与高效使用技巧》 引言 R语言作为一种功能强大的统计计算和图形显示语言,被广泛应用于科研、数据分析、金融等领域。R循环是R语言中的核心概念之一,对于提高编程效率、处理复杂数据至关重要。本文将深度解析R循环,并介绍高效使用技巧,帮助读者更好地掌握…

Windows WSL安装Emscripten‌/emsdk(失败)

前面说过&#xff0c;在CMD、PS下&#xff0c;根本安装不了。那只能试试在WSL安装了。 安装ubuntu 管理员运行PowerShell wsl --install ubuntu自带python3&#xff0c;不需要安装。 安装 我就奇怪了&#xff0c;都说得头头是道&#xff0c;实际上一操作就出问题&#xff…

SpringBoot + EMQX 通过MQTT协议和下位机建立通讯并获取下位机的监测数据-->测试

一、说明 在上一个博客中&#xff0c;我们部署了 EMQX 服务器&#xff0c;实现了 SpringBoot EMQX 通过MQTT协议和下位机建立通讯并获取下位机的监测数据的编码。现在我们对编码进行进一步的测试。 二、编码测试 2.1 在 EMQX 服务器开启客户端认证 这里的客户端认证&#xf…

#17 修改开源模型以适配新任务

在Kaggle的评论区看到有选手想用Swin Transformer来做回归任务,从这个片段可以窥见修改模型的一些思路,于是记录下来学习一下 基于 torchvision Swin_B 模型的特征提取 from torchvision.models.swin_transformer import swin_b, Swin_B_Weights, swin_v2_b, Swin_V2_B_Weig…

变电站自动化系统有哪些设备?

变电站自动化系统是由多类设备协同工作的复杂系统&#xff0c;其设备按功能层级可分为过程层设备​&#xff08;直接连接一次设备&#xff09;、间隔层设备​&#xff08;实现保护控制&#xff09;和站控层设备​&#xff08;数据汇总与监控&#xff09;&#xff0c;同时还包括…

树莓派超全系列教程文档--(66)rpicam-apps可用选项介绍之视频选项

这里写目录标题 视频选项qualitybitrateintraprofilelevelcodecsave-ptskeypresssignalinitialsplitsegmentcircularinlinelistenframesframerate 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 视频选项 本节中指定的命令行选项仅适用于视频输…

C++98和C++11的for循环使用对比

1、C98 中的 传统for 循环 语法结构&#xff1a;这种形式的 for 循环由三个部分组成&#xff1a;初始化语句&#xff08;int i 0&#xff09;、循环条件&#xff08;i < 10&#xff09;和迭代语句&#xff08;i&#xff09;。通常用于遍历一个已知大小的循环范围。使用场景…