当前位置: 首页 > news >正文

Python开发GUI常用库PyQt6和PySide6介绍之三:交互和通信方式讲解

Python开发GUI常用库PyQt6和PySide6介绍之三:交互和通信方式讲解

在PyQt6和PySide6中,事件(Event)和信号(Signal)是两个不同的概念,它们都是Qt框架中用于处理不同类型的应用程序响应机制。简言之,事件是对用户交互或系统状态变化的直接响应,而信号和槽是一种允许对象间通信的机制。在PyQt6和PySide6的应用程序中,合理地使用事件和信号机制对于创建响应灵敏且结构清晰的用户界面至关重要。

事件(Event)是由用户行为(如鼠标点击、按键等)或系统(如定时器超时、网络数据到达等)触发的。事件通常是由Qt的事件系统自动处理的,但是也可以通过重写事件处理器(event handler)来自定义处理逻辑。例如,如果你想自定义一个窗口组件的绘制行为,你可能会重写paintEvent方法。

信号(Signal)是Qt对象可以发出的一种通知,表明发生了某个动作或者某个状态已经改变。其他对象可以通过将槽函数(Slot)与这些信号连接起来来响应这些通知。这种信号和槽的机制是Qt的核心特性,它实现了对象之间的松耦合通信。例如,一个按钮(QPushButton)有一个clicked信号,当用户点击按钮时,这个信号就会被发出,如果有槽函数连接到这个信号,那么这些槽函数就会被调用。

事件是基于继承和重写的方式,通过重写事件处理方法来实现特定的响应;事件通过事件队列传递给相应的对象,并由对象的事件处理函数(如 mousePressEvent(), keyPressEvent() 等)进行处理。如果需要自定义事件处理,可以通过重写对象的事件处理函数来实现。

信号和槽的通信机制,属于观察者模式(Observer Pattern)或者说发布-订阅(publish-subscribe)模式的一种实现。信号和槽提供了一种更高级的、解耦的方式来处理对象之间的通信,一个对象不需要知道信号的接收者是谁,只需要发出信号即可。

当特定事件发生时,对象会发出(emit)一个信号,这个信号可以连接到一个或多个槽函数上,槽函数是实际处理信号的函数。信号和槽之间的连接可以跨越不同的对象,也就是说,一个对象的信号可以连接到另一个对象的槽函数上。

事件(Event

事件是Qt中的一个核心概念,用于处理用户的交互行为,它代表了应用程序中发生的事情,比如鼠标点击、按键按下、窗口移动等。每当这些事情发生时,Qt会创建一个事件对象(通常是QEvent类或其子类的实例),并将其发送给相应的组件去处理。组件可以通过重写特定的事件处理函数(如mousePressEvent(), keyPressEvent()等)来响应这些事件。

事件通常是由Qt的事件循环(Event Loop)处理和分发的,它们是同步的,意味着当事件发生时,相关的事件处理函数会被立即调用。

事件通常是由Qt的事件循环(Event Loop)处理和分派的。每当用户与界面交互时,比如移动鼠标或敲击键盘,一个事件就会被创建并加入到事件队列中。事件循环随后会处理这些事件,并将它们分派到相应的事件处理器(Event Handler)。

事件处理器(Event Handler

事件处理器是响应特定事件的函数或方法。在PyQt6和PySide6中,QWidget类及其子类都有一系列的事件处理器,可以被重写以实现自定义的事件处理逻辑。例如,如果你想要自定义一个窗口部件的绘制行为,你可以重写其paintEvent()方法。

事件的用途

  1. 用户交互:事件使得应用程序能够响应用户的各种交互,如点击、拖拽、滚动等。
  2. 窗口管理:事件用于处理窗口的变化,如大小调整、最小化、关闭等。
  3. 系统通知:事件还可以是系统级的,如定时器超时、网络数据到达等。
  4. 自定义行为:通过自定义事件和事件处理器,开发者可以实现特定的功能和行为。

在PyQt6和PySide6中,事件是由Qt框架自动产生并传递给相应的控件(widget)的。如果你想要自定义或者处理一个特定的事件,你需要在你的控件类中重写相应的事件处理方法。例如,如果你想要处理键盘按下事件,你需要重写keyPressEvent方法。

示例

下面是使用PyQt6库演示如何处理鼠标点击事件的简单例子:

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButtonclass MainWindow(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("Event Example")button = QPushButton("Click me!", self)button.clicked.connect(self.button_clicked)def button_clicked(self):print("Button clicked!")def keyPressEvent(self, event):if event.key() == Qt.Key.Key_Escape:print("Escape key pressed!")app = QApplication([])
window = MainWindow()
window.show()
app.exec()

这只是一个简单的示例,例子中,创建了一个继承自QMainWindow的主窗口类MainWindow。在窗口中,我们添加了一个按钮,并将其点击事件连接到button_clicked方法。当按钮被点击时,button_clicked方法会被调用,并打印"Button clicked!"。

使用PySide6库演示如何处理鼠标点击事件的简单例子,其与PyQt6类似,现在将上面代码改用PySide6实现,注意开头两行:

from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButtonclass MainWindow(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("Event Example")button = QPushButton("Click me!", self)button.clicked.connect(self.button_clicked)def button_clicked(self):print("Button clicked!")def keyPressEvent(self, event):if event.key() == Qt.Key.Key_Escape:print("Escape key pressed!")app = QApplication([])
window = MainWindow()
window.show()
app.exec()

信号(Signal

信号是Qt的另一个核心概念,它是一种实现对象之间通信的机制,特别是用于实现事件驱动编程中的松耦合。当一个对象的状态发生变化时,它可以发出一个信号,其他对象可以通过槽函数(Slot)来响应这个信号。

信号和槽机制是异步的,当一个信号被发出时,它不会立即调用槽函数。相反,信号会被放入一个队列中,然后在事件循环中适当的时候被处理。这允许应用程序在不同的时间点处理不同的信号,而不会阻塞当前的操作。

事件(Event)和信号(Signal)确实是两个不同的概念,但它们都是Qt框架用于处理应用程序中的不同类型的通信的机制。

区别

  • 事件是关于应用程序内部发生的事情,通常是用户的交互行为或者系统的通知。
  • 信号是对象之间通信的一种方式,用于通知其他对象某个动作已经发生或某个状态已经改变。

在实际使用中,你可能会看到事件和信号被同时使用。例如,一个按钮被点击时,它首先会收到一个鼠标点击事件,然后它可能会发出一个clicked信号,这个信号可以被其他任何关心这个按钮点击动作的对象所连接和处理。

总结来说,事件和信号在PyQt6和PySide6中都存在,它们是两种不同的机制,分别用于处理内部的用户界面事件和实现对象之间的通信。

槽(Slot)是信号和槽机制中的一个关键概念。槽可以被理解为一个接收信号的函数,当一个信号被发出时,连接到这个信号的所有槽函数都会被调用。

槽函数可以是任何可调用的Python函数或者对象的方法,它们用于响应信号。当你设计一个图形用户界面(GUI)时,你会定义槽函数来执行特定的任务,比如更新界面元素、处理数据或者与其他组件通信。

信号和槽的工作方式:

  1. 信号:当某个事件发生或对象状态改变时,对象会发出一个信号。
  2. :这是一个函数或方法,它被设计为响应信号。你可以将一个信号连接到一个或多个槽。
  3. 连接:在代码中,你会将信号和槽连接起来。当信号发出时,连接的槽函数会被调用。

槽函数不是事件处理程序,但它们可以作为事件处理的一部分。事件处理程序通常是响应特定事件(如鼠标点击、按键按下等)而被调用的函数。而槽函数是响应信号(由事件或其他条件触发)的函数。

信号和槽是Qt的一种通信机制,允许对象之间的解耦通信。当一个事件发生时,例如按钮被点击,一个信号会被发射。然后,任何连接到该信号的槽函数都会被调用。
在PyQt6中,使用pyqtSignal来创建一个信号,在PySide6中,你使用Signal。

示例

假设你有一个按钮(QPushButton),当用户点击这个按钮时,你希望更新标签(QLabel)的文本。你可以这样做,先看主要代码:

# 假设这是一个槽函数
def update_label():
    label.setText("按钮被点击了")

下面分别给出PyQt6和 PySide6实现。

两者的差别不大,表现在:from语句的前缀不同, PyQt6中的信号使用pyqtSignal,而PySide6中的信号使用Signal。

PyQt6实现的代码

☆使用系统提供的clicked信号实现情况,代码如下所示:

from PyQt6.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidgetclass MyWindow(QWidget):def __init__(self):super().__init__()self.setWindowTitle("示例")layout = QVBoxLayout()self.button = QPushButton("点击我")self.label = QLabel("初始文本")# 将按钮的 clicked 信号连接到槽函数self.button.clicked.connect(self.update_label)layout.addWidget(self.button)layout.addWidget(self.label)self.setLayout(layout)# 创建槽函数def update_label(self):self.label.setText("按钮被点击了")# 创建应用程序对象
app = QApplication([])# 创建自定义窗口对象
window = MyWindow()# 显示窗口
window.show()# 运行应用程序
app.exec()

在这个例子中,update_label 是一个槽函数,它被连接到按钮的 clicked 信号(使用的是系统提供的QPushButton类的clicked信号)。当按钮被点击时,clicked 信号被发出,然后 update_label 函数被调用,标签的文本随之更新。

总结来说,槽是信号和槽机制中用于响应信号的函数或方法,它们不是事件处理程序,但可以用于事件处理的流程中。

上述示例使用的是系统提供的QPushButton类的clicked信号。系统提供了许多预定义的信号,这些信号在Qt的各个组件中都有定义。clicked信号是QPushButton类的一个内置信号,当按钮被点击时发出。

☆使用自定义信号实现情况:

你还可以自定义信号,以便在特定场景下发出自己的信号,并将其连接到自定义的槽函数。下面是使用自定义信号实现上面的示例:

from PyQt6.QtCore import pyqtSignal
from PyQt6.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidgetclass MyWindow(QWidget):button_clicked = pyqtSignal() # 定义了一个名为button_clicked的自定义信号def __init__(self):super().__init__()self.setWindowTitle("示例")layout = QVBoxLayout()self.button = QPushButton("点击我")self.label = QLabel("初始文本")#将按钮的clicked信号连接到emit_button_clicked方法self.button.clicked.connect(self.emit_button_clicked) layout.addWidget(self.button)layout.addWidget(self.label)self.setLayout(layout)# emit_button_clicked方法def emit_button_clicked(self):self.label.setText("按钮被点击了")self.button_clicked.emit()# 创建应用程序对象
app = QApplication([])# 创建自定义窗口对象
window = MyWindow()# 创建槽函数
def update_label():window.label.setText("按钮被点击了")#将button_clicked信号连接到update_label槽函数
window.button_clicked.connect(update_label)# 显示窗口
window.show()# 运行应用程序
app.exec()

在这个例子中,我们定义了一个名为button_clicked的自定义信号,它是pyqtSignal类的实例。在按钮被点击时,我们通过调用emit()方法来发射这个自定义信号。

在main函数中,我们创建了一个update_label函数,用于更新标签的文本。我们将button_clicked信号连接到update_label槽函数,这样当自定义信号被发射时,槽函数将被触发。

通过使用自定义信号,我们可以在按钮被点击时发射信号,并将其连接到自定义的槽函数,以实现所需的功能。这种方式提供了更大的灵活性,允许你根据需要定义和使用自定义的信号。

PySide6实现的代码

☆使用系统提供的clicked信号实现情况,代码如下所示:

from PySide6.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidgetclass MyWindow(QWidget):def __init__(self):super().__init__()self.setWindowTitle("示例")layout = QVBoxLayout()self.button = QPushButton("点击我")self.label = QLabel("初始文本")# 将按钮的 clicked 信号连接到槽函数self.button.clicked.connect(self.update_label)layout.addWidget(self.button)layout.addWidget(self.label)self.setLayout(layout)# 创建槽函数def update_label(self):self.label.setText("按钮被点击了")# 创建应用程序对象
app = QApplication([])# 创建自定义窗口对象
window = MyWindow()# 显示窗口
window.show()# 运行应用程序
app.exec()

☆使用自定义信号实现情况:

from PySide6.QtCore import Signal
from PySide6.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidgetclass MyWindow(QWidget):button_clicked = Signal() # 定义了一个名为button_clicked的自定义信号def __init__(self):super().__init__()self.setWindowTitle("示例")layout = QVBoxLayout()self.button = QPushButton("点击我")self.label = QLabel("初始文本")#将按钮的clicked信号连接到emit_button_clicked方法self.button.clicked.connect(self.emit_button_clicked) layout.addWidget(self.button)layout.addWidget(self.label)self.setLayout(layout)# emit_button_clicked方法def emit_button_clicked(self):self.label.setText("按钮被点击了")self.button_clicked.emit()# 创建应用程序对象
app = QApplication([])# 创建自定义窗口对象
window = MyWindow()# 创建槽函数
def update_label():window.label.setText("按钮被点击了")#将button_clicked信号连接到update_label槽函数
window.button_clicked.connect(update_label)# 显示窗口
window.show()# 运行应用程序
app.exec()

信号-槽机制和事件机制是两种不同的交互和通信方式,刚开始学习理解难免比较模糊,下面是一些进一步解析。

在Qt C++、PyQt6和PySide6中,信号-槽机制和事件机制都非常常用,它们各自有不同的使用场景和优势。可以混合使用,而且这种混合使用是非常常见的,因为它们各自解决了不同的问题。

信号-槽机制主要用于对象间的通信。当一个事件发生时,比如用户点击了一个按钮,按钮对象会发出一个信号。这个信号可以连接到任何一个槽函数上,无论这个槽函数属于哪个对象,只要它们的参数类型兼容。这样,信号的发送者不需要知道接收者的具体情况,从而实现了对象之间的解耦。

事件机制则是用于处理Qt应用程序中的各种事件,如鼠标点击、键盘按键、定时器超时等。事件通常由Qt的事件循环分发给相应的对象,并且可以被这些对象重写的事件处理函数所处理。

在Qt C++、PyQt6和PySide6中,信号-槽机制和事件机制都非常常用,它们各自有不同的使用场景和优势。

信号-槽机制:

信号-槽机制是Qt框架的核心特性之一,它用于对象之间的通信。

  • 信号-槽机制是Qt特有的一种通信机制,允许对象之间的解耦通信。
  • 信号是一个对象可以发出的消息,表明某个事件发生了。
  • 槽是可以被信号触发的函数,它可以是任何可调用的代码块。
  • 信号和槽之间的连接是通过QObject的connect()函数建立的。

信号-槽机制实现方式,简单地说,通过在一个对象(通常是QObject的子类)中定义信号和槽,然后将信号和槽连接起来,当信号发出时,与之连接的槽会被调用。

应用场景: 适用于处理用户界面事件、对象之间的通信、跨线程通信等场景。在GUI编程中,常用于处理按钮点击、文本框输入等用户交互事件。

在开发GUI应用时,你会频繁地使用信号-槽机制来响应用户的操作,如按钮点击、菜单选择等。信号-槽机制的优势在于:

  • 解耦合: 信号发送者和槽接收者不需要知道对方的存在。
  • 简洁性: 通过简单的connect()调用即可建立复杂的通信机制。
  • 灵活性: 一个信号可以连接到多个槽,一个槽也可以接收多个信号。
  • 跨线程通信: 信号-槽机制可以安全地用于多线程环境,Qt会处理线程间的同步问题。

在设计模式中,信号-槽机制体现了观察者模式,这在GUI程序中是非常重要的。

事件机制:

事件机制是处理用户交互和系统事件的基础。

  • 事件机制是一种更为通用的编程模型,它不仅在Qt中存在,在许多其他编程框架中也有实现。
  • 事件是由应用程序或系统生成的,表明某种状态的改变或者用户交互的发生。
  • 事件通过事件队列传递,并由事件循环进行处理。
  • 事件处理通常是通过重写对象的事件处理函数(如mousePressEvent())来完成的。

实现方式实现方式,简单地说, 事件机制通常由事件循环、事件分发器和事件处理器组成。当事件发生时,事件分发器将事件分发给相应的事件处理器进行处理。

应用场景: 适用于处理底层系统事件、硬件事件、网络事件等各种类型的事件。在跨平台框架中,如Qt、wxWidgets等,事件机制通常用于处理系统级事件,例如窗口关闭、鼠标移动等。

事件机制允许程序响应如按键、鼠标移动、窗口重绘等事件。事件机制的优势在于:

  • 直接性: 事件直接发送到对象,由对象处理,这使得事件处理逻辑通常很直接。
  • 细粒度控制: 通过重写事件处理函数,可以精确控制对象对事件的响应。
  • 广泛性: 事件机制不仅用于用户交互,还用于系统级事件的处理。

在开发中,你可能需要注意的是:

  • 信号-槽机制 更适合用于处理不同对象之间的通信,尤其是当你需要将UI组件的行为与业务逻辑分离时。
  • 事件机制 更适合用于处理单个对象内部的状态变化,或者是对特定事件的响应。

在实际开发中,你会发现两者都非常重要,而且往往需要同时使用它们。例如,你可能会重写一个窗口的mousePressEvent()方法来处理鼠标点击事件(事件机制),然后在该方法内部发出一个信号,该信号连接到另一个对象的槽函数,以执行进一步的操作(信号-槽机制)。

信号-槽机制和事件机制都是Qt中用于处理对象间通信和用户交互的重要工具。信号-槽机制更侧重于对象间的解耦通信,而事件机制则侧重于对特定事件的响应处理。在实际应用中,两者往往会结合使用,以实现复杂的交互逻辑。例如,一个按钮的点击事件(事件机制)可能会触发一个信号,该信号又连接到一个槽函数,用于执行特定的操作。

混合使用的示例场景:

  1. 你可能会在一个窗口部件的事件处理函数(如mousePressEvent)中发出一个自定义信号,这个信号可以被连接到其他对象的槽上,以通知它们发生了某个事件。
  2. 你可能会在处理一个信号的槽函数中,根据需要生成一个新的事件并使用QCoreApplication::postEvent将其加入到事件队列中,以便稍后处理。
  3. 在某些情况下,你可能需要在事件处理函数中查询或修改与其他对象的状态,这时候可以通过发射信号与这些对象的槽函数进行交互。
  4. 你还可以在槽函数中调用update()或repaint()方法来请求重绘部件,这将导致一个绘制事件(paintEvent)被发送到事件队列。

信号-槽机制和事件机制是两种不同的交互和通信方式,它们在Qt框架中都得到了实现。

使用这两种机制的关键是理解它们的设计目的和最佳实践。信号-槽机制是为了对象间的解耦通信,而事件机制是为了处理和响应具体的事件。它们可以很好地协同工作,以创建一个响应灵敏且结构清晰的应用程序。

信号—槽机制的应用场景和事件机制的应用场景有重叠,尽管有一些重叠,信号和槽机制和事件机制在设计上有一些区别,它们并不是完全可替换的。下面是两者的一些关键区别。

信号-槽机制:

  • 用于对象间的通信。
  • 是一种观察者模式的实现,允许一个对象通知一个或多个其他对象发生了某个事件。
  • 信号-槽连接是在编译时或运行时建立的,可以动态地连接或断开。
  • 信号可以传递参数给槽函数,这些参数通常包含了触发信号的事件的相关信息。
  • 适用于实现不同组件之间的松耦合交互,如用户界面控件与应用逻辑之间的通信。

事件机制:

  • 用于处理输入(如鼠标、键盘)、定时器、绘图等事件。
  • 事件是由Qt的事件循环分发的,通常需要在目标对象中重写事件处理函数(如mousePressEvent、keyPressEvent)来响应事件。
  • 事件处理是同步的,即事件一旦被分发,目标对象的事件处理函数会立即执行。
  • 事件可以被忽略或接受,也可以被传递给父组件进行进一步处理。
  • 适用于处理和响应用户的交互操作以及系统的通知。

在实际应用中,通常会根据具体的需求选择合适的机制。例如,如果你需要处理用户的点击事件,你可能会重写mousePressEvent。如果你需要在不同的组件之间传递数据或通知,你可能会使用信号-槽机制。在复杂的应用程序中,两种机制往往会同时使用,以实现更灵活的交互和更好的代码组织。

PySide6官方相关资料

https://doc.qt.io/qtforpython/tutorials/basictutorial/signals_and_slots.html

https://doc.qt.io/qtforpython-6/PySide6/QtCore/QEvent.html#qevent

PyQt6官方相关资料

https://www.riverbankcomputing.com/static/Docs/PyQt6/signals_slots.html

https://www.riverbankcomputing.com/static/Docs/PyQt6/api/qtcore/qevent.html

相关文章:

  • 第八章 创建Callout Library - ZFentry 链接选项
  • Spring DefaultListableBeanFactory源码分析
  • mvtec3d
  • [架构之路-265]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 如何做好详细设计
  • D9741 PWM控制器电路,定时闩锁、短路保护电路,输出基准电压(2.5V) 采用SOP16封装
  • 【UE5.1】程序化生成Nanite植被
  • 实战10 角色管理
  • redis 从0到1完整学习 (八):QuickList 数据结构
  • Android画布Canvas drawPath绘制跟随手指移动的圆,Kotlin
  • Springcloud Alibaba 使用Canal将MySql数据实时同步到Elasticsearch
  • Git三种方法从远程仓库拉取指定分支
  • Leetcode 2971. Find Polygon With the Largest Perimeter
  • C#实现串口通讯
  • Unity Shader 实现X光效果
  • 【Qt-Event-信号和槽】
  • @angular/forms 源码解析之双向绑定
  • [译]前端离线指南(上)
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • Babel配置的不完全指南
  • maya建模与骨骼动画快速实现人工鱼
  • Netty源码解析1-Buffer
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • spring学习第二天
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 反思总结然后整装待发
  • 分布式任务队列Celery
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 通过git安装npm私有模块
  • 我的zsh配置, 2019最新方案
  • 一份游戏开发学习路线
  • 应用生命周期终极 DevOps 工具包
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • #define、const、typedef的差别
  • (1)(1.13) SiK无线电高级配置(六)
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (C语言)逆序输出字符串
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (Note)C++中的继承方式
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (五)MySQL的备份及恢复
  • (译) 函数式 JS #1:简介
  • (原創) 物件導向與老子思想 (OO)
  • .gitattributes 文件
  • .Net Remoting常用部署结构
  • .net 怎么循环得到数组里的值_关于js数组
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件