纯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_SIGNAL
和Q_SLOT
宏,或者在Qt 5及以后的版本中使用signals
和slots
关键字,可以很容易地定义它们。
信号与槽的优势
- 松耦合:发出信号的对象不需要知道哪个槽会接收这个信号。
- 类型安全:信号和槽的参数类型必须匹配,这减少了类型错误的可能性。
- 跨线程通信: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 =========================
高级、
那么通过上面的学习,我们要干票大的,能否在类中使用信号,编写一个信号的管理类,一个类中单独的信号管理器,用于管理类中的所有信号;一个全局信号管理器,用于管理整个代码中的信号。
未完待续。