纯Python实现Qt的信号与槽机制

纯Python实现Qt的信号与槽机制

Qt中的信号与槽详解


在Qt框架中,信号与槽是一种非常有特色的机制,用于对象之间的通信。这一机制是Qt中实现事件驱动编程的核心部分。下面我将详细解释信号与槽的概念和它们是如何工作的。

信号(Signals)

信号是一个由对象发出的消息,表明发生了一个特定的事件。当对象内部的状态发生变化时,信号就被发出。例如,当一个按钮被点击时,它就会发出一个clicked信号。
信号可以携带任意数量的参数,但它们不能有返回值。发出信号的对象不关心是否有其他的对象监听这个信号,这是所谓的“fire and forget”机制。

槽(Slots)

槽是响应特定信号的函数。槽可以用来处理信号,执行一些操作,比如更新用户界面、处理数据等。槽是普通的C++成员函数,它们可以接受任意数量的参数,并且可以有返回值。

emit关键字

在Qt中,emit关键字用于从对象中发射一个信号。当你在类的实现中使用emit时,你是在告诉Qt框架你想要发出一个信号,这样连接到这个信号的所有槽都会被调用。

这里是一个简单的例子,展示了如何在自定义的Qt对象中发出信号:

// MyObject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H#include <QObject>class MyObject : public QObject
{Q_OBJECTpublic:explicit MyObject(QObject *parent = nullptr);signals:void mySignal(); // 定义一个信号public slots:void onMySignal(); // 定义一个槽
};#endif // MYOBJECT_H// MyObject.cpp
#include "MyObject.h"MyObject::MyObject(QObject *parent) : QObject(parent)
{
}void MyObject::onMySignal()
{// 槽函数的实现qDebug() << "Signal received!";
}void MyObject::someFunction()
{// 在某个条件下发出信号if (someCondition()) {emit mySignal(); // 使用emit发出信号}
}

在这个例子中,MyObject类有一个名为mySignal的信号和一个名为onMySignal的槽。当someFunction函数中的条件满足时,mySignal信号会被发出。任何连接到这个信号的槽都会被调用,例如在这个例子中,onMySignal槽会输出一条消息。

信号与槽的连接

在Qt中,一个对象的信号可以与另一个对象的槽函数相连接。当信号发出时,连接的槽函数将被自动调用。这种机制实现了对象之间的解耦,因为发出信号的对象不需要知道哪个槽会响应它。
连接信号与槽可以使用connect函数,这个函数是QObject类的一个成员函数,几乎所有的Qt类都继承自QObject。连接可以是同步的,也可以是异步的,并且可以指定连接的类型,比如一个信号可以连接到多个槽,或者多个信号可以连接到一个槽。

示例

QPushButton *button = new QPushButton("Click me!");
QObject::connect(button, &QPushButton::clicked, this, &MyClass::handleClick);

在上面的例子中,我们创建了一个按钮,并将其clicked信号连接到了MyClass类的handleClick槽函数上。当按钮被点击时,handleClick函数将被调用。

自定义信号与槽

除了使用Qt提供的预定义信号和槽,你还可以定义自己的信号和槽。通过使用Q_SIGNALQ_SLOT宏,或者在Qt 5及以后的版本中使用signalsslots关键字,可以很容易地定义它们。

信号与槽的优势

  • 松耦合:发出信号的对象不需要知道哪个槽会接收这个信号。
  • 类型安全:信号和槽的参数类型必须匹配,这减少了类型错误的可能性。
  • 跨线程通信:Qt支持在不同线程中的对象之间进行信号与槽的连接,这使得多线程编程更加简单。
    信号与槽机制是Qt框架中实现对象间通信的关键特性,理解这一机制对于开发高效和可维护的Qt应用程序至关重要。

Python实现


Qt的信号与槽机制有点像Go语言中channel(通道)模型,Go语言的通道(channel)是用于在goroutine之间进行通信的内置类型。通道可以传输特定类型的数据,并且提供了一种在goroutine之间同步数据交换的方式,而无需使用显式的锁或条件变量。

定义channel

我们用一个列表模仿来模仿channel(通道)的效果。列表的第一个元素是信号的名称,除第一个元素以外的地方,用来存放槽函数。
定义如下:

# Global signal chaneels
# Define the different signal channels as lists with an identifier at index 0
# You can customize the channel like below
CONFIG_MODIFIED = ["CONFIG_MODIFIED"]
APP_STATUS = ["APP_STATUS"]
# Define the ALL_CHANNELS list to hold all signal channels
ALL_CHANNELS = [CONFIG_MODIFIED, APP_STATUS]

connect和disconnect

有必要去了解python内置的callable()函数。在Python中,callable()是一个内置函数,它可以用来检查一个对象是否是可调用的。如果一个对象是可调用的,那么它可以被直接调用,比如函数、类实例或类。

callable()函数的返回值是一个布尔值:如果对象是可调用的,它返回True;否则,返回False。

以下是一些使用callable()函数的例子:

# 检查一个函数是否可调用
print(callable(print))  # 输出: True# 检查一个类是否可调用(通常类是可调用的,除非它是抽象基类)
class MyClass:passprint(callable(MyClass))  # 输出: True# 检查一个类实例是否可调用
obj = MyClass()
print(callable(obj))  # 输出: True# 检查一个普通的对象是否可调用
obj = object()
print(callable(obj))  # 输出: False# 检查一个字典是否可调用
obj = {}
print(callable(obj))  # 输出: False# 检查一个列表是否可调用
obj = []
print(callable(obj))  # 输出: False

在上述例子中,我们可以看到print函数、类MyClass、类实例obj和类本身都是可调用的,因为它们可以被直接调用。而普通对象、字典和列表是不可调用的,因为它们没有__call__方法,这是所有可调用对象都必须实现的魔法方法。

connect

如果receiver是函数就添加到channel中。

# Function to connect a receiver (callback) to a signal channel
def connect(channel, receiver):"""Connects a signal receive method (receiver) to the provided channel.The receiver will be called when the signal is emitted on this channel."""# Check if the receiver is callable (e.g., a function or method)if callable(receiver):try:# Append the receiver to the channel's list of receiverschannel.append(receiver)except Exception:# Log an exception if the receiver cannot be connectedmsg = "Cannot connect to channel <%s> receiver: %s"LOG.exception(msg, channel[0], receiver)
disconnect

从channel中移除槽函数。

# Function to disconnect a receiver from a signal channel
def disconnect(channel, receiver):"""Disconnects a signal receive method (receiver) from the provided channel.The receiver will no longer be called when the signal is emitted on this channel."""# Check if the receiver is callableif callable(receiver):try:# Remove the receiver from the channel's list of receiverschannel.remove(receiver)except Exception:# Log an exception if the receiver cannot be disconnectedmsg = "Cannot disconnect from channel <%s> receiver: <%s>"LOG.exception(msg, channel[0], receiver)

发射(emit)信号

通过遍历通道中的槽函数并执行。

# Function to emit a signal to all receivers in a channel
def emit(channel, *args):"""Sends a signal to all receivers connected to the provided channel.Passes any additional arguments to the receivers."""# Iterate over all receivers in the channel (starting from index 1, as index 0 is the channel name)for receiver in channel[1:]:try:# Check if the receiver is callable and call it with the provided argumentsif callable(receiver):receiver(*args)except Exception:# Log an exception if there's an error calling the receivermsg = "Error calling <%s> receiver %s with %s"LOG.exception(msg, channel[0], receiver, args)continue

channel相关

# Function to clean a single channel, removing all receivers
def clean_channel(channel):"""Cleans a channel by removing all receivers, leaving only the channel name."""# Store the channel namename = channel[0]# Clear the channel listchannel[:] = []# Append the channel name back to the listchannel.append(name)# Function to clean all channels, removing all receivers from each channel
def clean_all_channels(channels=ALL_CHANNELS):"""Cleans all channels by removing all receivers from each channel."""# Iterate over all channels in the channels listfor item in channels:# Clean each channelclean_channel(item)

示例

# Example usage:
if __name__ == "__main__":# Define the signal channels as list with an identifier at index 0signal = ["signal"]all_signal = [signal,]# Define a slot function that will be called when a signal is emitteddef my_slot(*args):print("Signal received with arguments:", args)def my_slot1(str1, str2):print("This is a slot,arg1:{},arg2:{}.".format(str1, str2))# Connect the slot to a signal channelconnect(signal, my_slot)connect(signal, my_slot1)print("connect slot:", signal)  # print channel that signal `connect` slot# Emit a signal on the channelemit(signal, "attr", "value")print("emit signal:", signal)  # print channel that signal `emit` slot# Later, if you want to stop receiving signals, disconnect the slotdisconnect(signal, my_slot)print("disconnect my_slot:", signal)  # print channel that signal `disconnect` slot# Emitting the signal now will not call my_slot anymoreemit(signal, "attr", "new_value")# clean channelclean_channel(signal)print("clean channel:", signal)print(signal)# clean all channelsclean_all_channels(all_signal)print(all_signal)

进阶,实现Signal类


class Signal:"""The Signal class provides a Qt-like signal-slot functionality.It allows connecting receivers (slots) to signals and emitting signalsto call all connected receivers with the provided arguments."""# Initialize the list of receivers with a placeholder for the signal namedef __init__(self, signal_name: str):"""Initializes the Signal instance with the given signal name.The signal name is used for identification and logging purposes."""# Ensure the signal_name is a stringif not isinstance(signal_name, str):raise TypeError("Signal name must be a string")# Initialize the list of receivers with the signal name as the first elementself._receivers = [signal_name]def connect(self, receiver):"""Connects a receiver (slot) to the signal.The receiver will be called when the signal is emitted."""connect(self._receivers, receiver)def disconnect(self, receiver):"""Disconnects a receiver (slot) from the signal.The receiver will no longer be called when the signal is emitted."""disconnect(self._receivers, receiver)def emit(self, *args):"""Emits the signal, calling all connected receivers with the provided arguments."""emit(self._receivers, *args)def clean(self):"""Cleans the signal, removing all connected receivers."""clean_channel(self._receivers)

示例

# Example usage:
if __name__ == "__main__":# Create a signal instancemy_signal = Signal("clicked")# Define a slot functiondef my_slot(*args):print("Clicked Signal received with arguments:", args)# Connect the slot to the signalmy_signal.connect(my_slot)# Emit the signal with some argumentsmy_signal.emit("Hello", "World")# Disconnect the slotmy_signal.disconnect(my_slot)# Emit the signal again (my_slot should not be called)my_signal.emit("Hello", "Again")# ================ Example two =========================

高级、


那么通过上面的学习,我们要干票大的,能否在类中使用信号,编写一个信号的管理类,一个类中单独的信号管理器,用于管理类中的所有信号;一个全局信号管理器,用于管理整个代码中的信号。
未完待续。

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

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

相关文章

【复现】用友NC-Cloud文件上传漏洞_70

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 用友NC Cloud大型企业数字化平台&#xff0c;深度应用新一代数字智能技术&#xff0c;完全基于云原生架构&#xff0c;打造开放、…

碳交易机制下考虑需求响应的优化运行-MATLAB复现

读完《碳交易机制下考虑需求响应的综合能源系统优化运行——魏震波》这篇文章后&#xff0c;我对咱们能源系统的未来充满了期待。文章里头提到的那些复杂的数学模型和算法&#xff0c;虽然听起来有点高大上&#xff0c;但其实它们就像是给能源系统装上了一个智能大脑&#xff0…

vue3实现导出pdf、png功能

准备做的系统中出现了 想导出当前页面的png或者pdf设计数据较多后端做可能比较麻烦 就自己研究了一下 1、安装html2canvas 、jspdf包 npm install --save html2canvas // 可以将dom元素转为一张图片 npm install --save jspdf // 导出为PDF格式 2、vue组件中引用&#x…

【石上星光】context,go的上下文存储并发控制之道

目录 1 引言2 What&#xff1f;3 How&#xff1f; 3.1 用法一、上下文数据存储3.2 用法二、并发控制 3.2.1 场景1 主动取消3.2.2 场景2 超时取消 3.3 用法三、创建一个空Context&#xff08;emptyCtx&#xff09; 4 Why&#xff1f; 4.1 go中的上下文思想 4.1.1 上下文是什么…

html展示微信小程序二维码

1.生成微信小程序二维码 获取不限制的小程序码 | 微信开放文档 2.经过验证&#xff0c;下面两种方式可行 2.1直接将官方生成的内容直接在src标签里面是可以展示的 接口要支持GET请求 html代码 <body > <!--get请求可以&#xff0c;post请求不行--> <img src…

DataLoader类

DataLoader 类是 PyTorch 中用于构建数据加载器的一个重要工具&#xff0c;它可以对数据集进行批处理、洗牌和并行加载&#xff0c;以便于训练神经网络模型。 ### 输入参数&#xff1a; - **dataset**&#xff1a;数据集对象&#xff0c;通常是 torch.utils.data.Dataset 类的…

python爬虫———激发学习兴趣的案列(第十三天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

工业通信原理——CAN通信精讲

工业通信原理——CAN通信精讲 前言 CAN总线是一种用于在控制系统中进行通信的串行总线标准,常用于汽车、工业控制等领域。它允许多个设备在同一总线上进行通信,而不需要中央控制器,因此具有分布式、实时性强等特点。 CAN总线的报文格式通常分为两种:数据帧(Data Frame)…

渲染农场实时画面怎么设置?云渲染农场实时预览效果查看

许多用户在使用渲染农场服务时&#xff0c;常常难以找到查看实时渲染画面的功能。由于渲染是一个时间消耗较大的任务&#xff0c;如果最终结果与预期不符&#xff0c;可能会对整个工作流程产生负面影响。因此&#xff0c;渲染平台若能提供实时预览渲染进度和效果的功能&#xf…

代码随想录算法训练营第五十天 | 123. 买卖股票的最佳时机 III、188. 买卖股票的最佳时机 IV

代码随想录算法训练营第五十天 | 123. 买卖股票的最佳时机 III、188. 买卖股票的最佳时机 IV 123. 买卖股票的最佳时机 III题目解法 188. 买卖股票的最佳时机 IV题目解法 感悟 123. 买卖股票的最佳时机 III 题目 解法 题解链接 1. class Solution { public:int maxProfit(ve…

【汇编语言实战】求三个已知数最大值

C语言描述该程序流程&#xff1a; #include <stdio.h> int main() {int a10,b20,c15;//scanf("%d %d",&a,&b);if(a>b){if(a>c){printf("%d",c);}else{printf("%d",a);}}else{if(b>c){printf("%d",b);}else{pr…

【C++】掌握C++函数重载和引用开启代码优化的新篇章

欢迎来CILMY23的博客 本篇主题为 掌握C函数重载和引用开启代码优化的新篇章 个人主页&#xff1a;CILMY23-CSDN博客 个人专栏&#xff1a; | | | CILMY23-CSDN博客 上一篇博客&#xff1a;第一个C结构&#xff0c;C关键字&#xff0c;命名空间&#xff0c;C的输入输出&…

IPEX-LLM(原名BigDL-LLM)环境配置

IPEX-LLM 是一个为Intel XPU (包括CPU和GPU) 打造的轻量级大语言模型加速库&#xff0c;在Intel平台上具有广泛的模型支持、最低的延迟和最小的内存占用。 您可以使用 IPEX-LLM 运行任何 PyTorch 模型&#xff08;例如 HuggingFace transformers 模型&#xff09;。在运行过程中…

TTL接口的输入输出

The Ins and Outs of the TTL Interface 串行通信可在相当长的距离内传输数据&#xff0c;通常与 TTL 标准有关。数据通过串行通信传输&#xff0c;串行通信通过单线传输比特位。数据通过双方--发送方和接收方--以二进制脉冲的形式使用各种串行数字二进制技术进行交换。 RS232 …

jquery按回车切换下一个文本框

/** *按回车切换下一个文本框 *param field *param event *returns Boolean *οnkeypress"return handleEnter(this,event)" */ function handleEnter(field,event){ var keyCode event.keyCode ? event.keyCode : event.which ? event.which :event.ch…

Sql缺失索引查询,自动创建执行语句

试图查询确实的索引 CREATE VIEW [dbo].[vw_Index_MissingIndex] ASSELECT [ d.name ] as DBName,[dbo].[fn_Index_CreateIndexName](mid.equality_columns,mid.Inequality_columns,mid.index_handlE) AS ID,REPLACE(mid.equality_columns,,, ASC,) AS equality_columns,REP…

Redis面试题1

Redis基础概念 1.请简述Redis是什么&#xff1f; 答&#xff1a;Redis是一个开源的使用ANSI C语言编写的、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。它通常被称为数据结构服务器&#xff0c;因为值&#xff0…

《C++程序设计》阅读笔记【7-堆和拷贝构造函数】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;《C程序设计》阅读笔记 本文对应的PDF源文件请关注微信公众号程序员刘同学&#xff0c;回复C程序设计获取下载链接。 1 堆与拷贝构造函数1.1 概述1.2 分配堆对象1.3 拷贝构造函数1.3.1 默…

301永久重定向与302临时重定向的正确运用

我们前不久有Hostease的客户的网站进行了域名更改。客户想了解域名更改后会有哪些后果和影响。一般网站域名更换后会遇到旧页面失效问题。为了优化用户体验和维护搜索引擎优化(SEO)&#xff0c;我们需要正确地使用301永久重定向和302临时重定向。这两种重定向方式各有其特点和适…

本地linux怎样先亚马逊云服务器上传文件

使用亚马逊云服务器的密钥&#xff0c;并通过srp命令&#xff0c; 连接上传&#xff1a; scp -i <密钥文件路径> <本地文件路径> <远程主机用户名><远程主机地址>:<目标路径> scp -i assig2.pem data.txt ubuntuec2-xx-xxx-xx-xx1.compute-x.…