资讯动态

PyQt5 事件与信号机制完全指南:从主循环到自定义信号的实战教程

发布时间:2026/10/4 11:00:11 来源:尧图企业网站定制
文档教程【免费下载链接】PyQt-Chinese-tutorialPyQt6中文教程项目地址https://gitcode.com/gh_mirrors/py/PyQt-Chinese-tutorial点击查看免费下载本文是 PyQt5 中文教程系列中关于「事件和信号」章节的深度展开。GUI 程序本质上是事件驱动的理解事件模型、信号signal与槽slot机制是掌握 PyQt5 应用开发的核心前提。读完本文你将掌握事件模型三要素、用connect()绑定信号与槽、重写事件处理器、读取事件对象数据、用sender()定位事件来源以及用pyqtSignal发送自定义信号并了解 PyQt5 与 PyQt6 在 API 层面的差异。事件驱动模型GUI 应用的核心运行机制所有 GUI 应用程序都是事件驱动的。事件event大部分由用户行为产生比如鼠标点击、键盘按键、窗口拖动但也存在其他事件来源例如网络连接状态变化、窗口管理器的通知、定时器超时等。当我们调用应用程序的exec_()方法时在 PyQt6 中为exec()应用会进入主循环main loop。主循环持续获取系统派发的事件并将它们分发到对应的对象去处理。这构成了 PyQt5 程序从启动到退出的基本运转方式。事件模型的三个参与者在 PyQt5 的事件模型中始终存在三个角色角色英文作用事件源event source状态发生改变的对象负责产生事件事件对象event object封装事件源中状态变化的具体内容即事件本身事件目标event target希望被通知、接收事件并做出反应的对象事件源对象会把处理事件的任务委托给事件目标。举例来说用户拖动滑块时QSlider是事件源其内部值的变化被封装成事件对象而QLCDNumber作为事件目标接收通知并刷新显示。信号与槽PyQt5 处理事件的独特机制PyQt5 使用一套独有的信号与槽signal and slot机制来处理事件它用于对象之间的通讯。当某个特定事件发生时会发射一个信号signal而槽slot可以是任意可调用的 Python 对象——一个函数、一个方法甚至是内置的close()或display()。槽只有在与其相连的信号被发射时才会被调用可以把它理解为事件绑定的处理函数类似回调函数的概念。需要注意PyQt5 已经从机制上废弃了旧版SIGNAL()/SLOT()的调用方式改用更简洁的.connect()写法这一点在 translated/pyqt5/index.md 的 PyQt4 与 PyQt5 区别中有明确说明。信号与槽入门滑块与数字显示联动下面是最经典的信号与槽演示把QSlider的valueChanged信号连接到QLCDNumber的display槽上拖动滑块时液晶数字实时变化。#!/usr/bin/python3 # -*- coding: utf-8 -*- ZetCode PyQt5 tutorial In this example, we connect a signal of a QSlider to a slot of a QLCDNumber. Author: Jan Bodnar Website: zetcode.com Last edited: January 2017 import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider, QVBoxLayout, QApplication) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): lcd QLCDNumber(self) sld QSlider(Qt.Horizontal, self) vbox QVBoxLayout() vbox.addWidget(lcd) vbox.addWidget(sld) self.setLayout(vbox) sld.valueChanged.connect(lcd.display) self.setGeometry(300, 300, 250, 150) self.setWindowTitle(Signal and slot) self.show() if __name__ __main__: app QApplication(sys.argv) ex Example() sys.exit(app.exec_())这个例子中展示了QLCDNumber液晶数字显示器和QSlider滑块两个控件通过垂直布局QVBoxLayout上下排列。核心绑定只有一行sld.valueChanged.connect(lcd.display)这里把滑块的值变化信号valueChanged与液晶数字的display槽绑定在一起。滑块每次改变数值都会发射带新数值的信号lcd.display槽接收该数值并刷新显示。在这个机制中sender发送者是发射信号的对象这里是滑块receiver接收者是接收信号的对象这里是液晶显示器slot槽是对信号做出反应的方法display。程序运行效果如下扩展阅读本仓库其他章节中大量使用了这种绑定方式例如 translated/pyqt5/hello_world.md 中的qbtn.clicked.connect(QCoreApplication.instance().quit)用按钮的clicked信号触发应用退出translated/pyqt5/dui-hua-kuang.md 中的self.btn.clicked.connect(self.showDialog)用按钮触发对话框弹出translated/pyqt5/cai-dan-he-gong-ju-lan.md 中的exitAct.triggered.connect(qApp.quit)则用菜单动作的triggered信号绑定退出。可以看到无论控件类型如何变化信号与槽的绑定模式是完全一致的。重写事件处理器拦截键盘按键在 PyQt5 中事件处理器event handler经常需要被重写即用我们自己的实现覆盖库自带的默认实现。所有事件处理器都有默认行为有些默认实现包含内部逻辑如拖选、点击有些只是空函数空函数需要覆盖以实现事件处理而带默认逻辑的处理器也可以按需覆盖例如禁用自带的拖选或重写拖选效果。下面的例子重写了keyPressEvent()事件处理器实现按 ESC 键关闭窗口#!/usr/bin/python3 # -*- coding: utf-8 -*- ZetCode PyQt5 tutorial In this example, we reimplement an event handler. Author: Jan Bodnar Website: zetcode.com Last edited: August 2017 import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QApplication class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 250, 150) self.setWindowTitle(Event handler) self.show() def keyPressEvent(self, e): if e.key() Qt.Key_Escape: self.close() if __name__ __main__: app QApplication(sys.argv) ex Example() sys.exit(app.exec_())重写的关键方法def keyPressEvent(self, e): if e.key() Qt.Key_Escape: self.close()keyPressEvent是QWidget提供的键盘事件处理器参数e是事件对象。当检测到e.key()返回的按键码等于Qt.Key_Escape时调用self.close()关闭窗口。此时如果按下 ESC 键程序就会退出。这个窗口只有一个空白框没有其他界面元素因此原文档没有给出运行截图——这正是理解事件处理器默认实现往往是空函数的好例子我们不添加任何控件仅仅通过重写键盘事件处理器就赋予了窗口新的行为。PyQt5 与 PyQt6 的写法差异同样的功能在 PyQt6 中写法略有不同见 translated/pyqt6/eventssignals.md。PyQt6 中按键枚举从Qt.Key_Escape变为Qt.Key.Key_Escape.value主循环入口从app.exec_()变为app.exec()。从源码结构看PyQt6 对枚举和命名空间做了更严格的封装迁移时需注意这些细节。事件对象从鼠标事件中读取坐标事件对象event object是用 Python 描述事件自身一系列属性的对象具体包含哪些属性取决于触发的事件类型。下面的示例在QLabel组件中实时显示鼠标指针的 X、Y 坐标#!/usr/bin/python3 # -*- coding: utf-8 -*- ZetCode PyQt5 tutorial In this example, we display the x and y coordinates of a mouse pointer in a label widget. Author: Jan Bodnar Website: zetcode.com Last edited: August 2017 import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QLabel class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): grid QGridLayout() grid.setSpacing(10) x 0 y 0 self.text x: {0}, y: {1}.format(x, y) self.label QLabel(self.text, self) grid.addWidget(self.label, 0, 0, Qt.AlignTop) self.setMouseTracking(True) self.setLayout(grid) self.setGeometry(300, 300, 350, 200) self.setWindowTitle(Event object) self.show() def mouseMoveEvent(self, e): x e.x() y e.y() text x: {0}, y: {1}.format(x, y) self.label.setText(text) if __name__ __main__: app QApplication(sys.argv) ex Example() sys.exit(app.exec_())代码分三层理解1. 初始化显示标签。用字符串格式化构造初始文本并创建QLabelself.text x: {0}, y: {1}.format(x, y) self.label QLabel(self.text, self)X、Y 坐标显示在QLabel组件中通过QGridLayout布局并设置Qt.AlignTop让标签靠顶部对齐。2. 开启鼠标跟踪self.setMouseTracking(True)事件追踪默认没有开启。默认情况下组件只有在鼠标移动时按住至少一个鼠标按键才会收到鼠标移动事件开启鼠标跟踪后即使不按下任何按键仅仅移动鼠标也会持续收到移动事件。这正是本例能够实时刷新坐标的前提。3. 重写鼠标移动处理器def mouseMoveEvent(self, e): x e.x() y e.y() text x: {0}, y: {1}.format(x, y) self.label.setText(text)e代表事件对象其中封装了我们触发的事件鼠标移动事件的数据。通过e.x()和e.y()方法获取鼠标指针的坐标拼成字符串后通过setText()输出到QLabel组件。程序运行效果PyQt5 与 PyQt6 的写法差异在 PyQt6 中鼠标坐标的获取方式有所变化见 translated/pyqt6/eventssignals.md。PyQt6 使用e.position().x()和e.position().y()返回浮点数通常用int()取整替代了 PyQt5 中直接调用e.x()/e.y()的写法对齐方式也从Qt.AlignTop变为Qt.Alignment.AlignTop。从源码结构看这是 PyQt6 强化类型命名空间的结果。事件发送者用 sender() 定位信号来源有时候我们需要知道究竟是哪个组件发出了信号PyQt5 提供的sender()方法可以完成这件事。下面的例子有两个按钮点击任意一个状态栏会显示被点击按钮的文字#!/usr/bin/python3 # -*- coding: utf-8 -*- ZetCode PyQt5 tutorial In this example, we determine the event sender object. Author: Jan Bodnar Website: zetcode.com Last edited: August 2017 import sys from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): btn1 QPushButton(Button 1, self) btn1.move(30, 50) btn2 QPushButton(Button 2, self) btn2.move(150, 50) btn1.clicked.connect(self.buttonClicked) btn2.clicked.connect(self.buttonClicked) self.statusBar() self.setGeometry(300, 300, 290, 150) self.setWindowTitle(Event sender) self.show() def buttonClicked(self): sender self.sender() self.statusBar().showMessage(sender.text() was pressed) if __name__ __main__: app QApplication(sys.argv) ex Example() sys.exit(app.exec_())关键点在于两个按钮连接到了同一个槽btn1.clicked.connect(self.buttonClicked) btn2.clicked.connect(self.buttonClicked)两个按钮都和同一个buttonClicked槽绑定。在槽内部用sender()区分事件源def buttonClicked(self): sender self.sender() self.statusBar().showMessage(sender.text() was pressed)调用sender()方法返回发射信号的控件对象再通过sender.text()取得按钮文字拼成 Button 1 was pressed 之类的消息显示在状态栏中。这样多个控件共享一个槽函数时也能精确知道是谁触发的。程序运行效果扩展阅读sender()在仓库的俄罗斯方块示例中有更实用的应用场景见 translated/pyqt5/e-luo-si-fang-kuai-you-xi.md。该示例用self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)将棋盘发来的状态消息直接路由到主窗口状态栏正是一个信号连接一个槽、由接收者决定如何反应的典型用法。发射自定义信号用 pyqtSignal 与 emitQObject实例能够发送事件信号。内置信号之外我们还可以自定义信号。下面的例子创建了一个名为closeApp的信号鼠标点击窗口时发射该信号从而关闭应用#!/usr/bin/python3 # -*- coding: utf-8 -*- ZetCode PyQt5 tutorial In this example, we show how to emit a custom signal. Author: Jan Bodnar Website: zetcode.com Last edited: August 2017 import sys from PyQt5.QtCore import pyqtSignal, QObject from PyQt5.QtWidgets import QMainWindow, QApplication class Communicate(QObject): closeApp pyqtSignal() class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.c Communicate() self.c.closeApp.connect(self.close) self.setGeometry(300, 300, 290, 150) self.setWindowTitle(Emit signal) self.show() def mousePressEvent(self, event): self.c.closeApp.emit() if __name__ __main__: app QApplication(sys.argv) ex Example() sys.exit(app.exec_())分三步理解这个自定义信号流程1. 定义信号。自定义信号必须作为类的属性声明且类需要继承自QObjectclass Communicate(QObject): closeApp pyqtSignal()Communicate类通过类属性pyqtSignal()创建了一个无参数信号closeApp。如果信号需要携带数据可以在pyqtSignal中指定类型例如pyqtSignal(str)表示信号会携带一个字符串参数。2. 连接信号与槽self.c Communicate() self.c.closeApp.connect(self.close)实例化Communicate后把自定义的closeApp信号与QMainWindow的close()方法绑定。close()在这里作为槽使用。3. 在事件处理器中发射信号def mousePressEvent(self, event): self.c.closeApp.emit()重写mousePressEvent当鼠标点击窗口时调用emit()发射closeApp信号随即触发已绑定的close()槽程序终止。这个窗口同样没有可见的界面元素因此原文档未提供运行截图。带参数的信号实战自定义信号携带参数在仓库的俄罗斯方块游戏中有完整实践见 translated/pyqt5/e-luo-si-fang-kuai-you-xi.md。其中Board类定义了msg2Statusbar pyqtSignal(str)这样的带字符串参数信号在消除行数变化、暂停、游戏结束时调用self.msg2Statusbar.emit(str(self.numLinesRemoved))或self.msg2Statusbar.emit(Game over)等方式发射消息主窗口通过self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)完成连接。这展示了自定义信号在真实游戏中的完整生命周期定义声明类型→ 绑定connect→ 触发emit→ 消费状态栏显示。PyQt5 与 PyQt6 信号槽写法对照本仓库同时收录了 PyQt5 与 PyQt6 两版教程translated/pyqt6/eventssignals.md 与本文主题一一对应。为方便迁移整理核心差异如下依据两个版本的文档内容功能点PyQt5PyQt6主循环入口app.exec_()app.exec()水平滑块枚举Qt.HorizontalQt.Orientation.Horizontal按键枚举Qt.Key_EscapeQt.Key.Key_Escape.value对齐枚举Qt.AlignTopQt.Alignment.AlignTop鼠标坐标e.x()/e.y()e.position().x()/e.position().y()字符串格式化x: {0}.format(x)fx: {x}从两个版本的源码结构看PyQt6 对枚举类型、命名空间和事件数据接口做了更严格的封装但信号与槽的核心机制完全一致connect()绑定、emit()发射、sender()溯源、pyqtSignal自定义信号这些 API 在两个版本中保持相同的设计哲学。学会 PyQt5 的事件与信号机制迁移到 PyQt6 只需要按上表调整 API 写法即可。小结事件与信号机制贯穿所有 PyQt5 应用程序通过exec_()进入主循环后持续监听并分发事件事件模型由事件源、事件对象、事件目标三要素构成信号与槽是对象间通讯的纽带。本文通过五个完整的可运行示例覆盖了信号槽绑定QSliderQLCDNumber、事件处理器重写keyPressEvent、事件对象读取鼠标坐标、事件来源定位sender()以及自定义信号发射pyqtSignalemit并对比了 PyQt5 与 PyQt6 的写法差异。这些能力是继续学习布局管理translated/pyqt5/bu-ju-guan-li.md、对话框translated/pyqt5/dui-hua-kuang.md、绘图translated/pyqt5/hui-tu.md等后续章节的基础。赞分享文档教程【免费下载链接】PyQt-Chinese-tutorialPyQt6中文教程项目地址https://gitcode.com/gh_mirrors/py/PyQt-Chinese-tutorial点击查看免费下载相关推荐TripoSR完整教程一张图片0.5秒变成3D模型的开源方案TripoSR完整教程一张图片0.5秒变成3D模型的开源方案 TripoSR 是由 Tripo AI 与 Stability AI 联合发布的开源单图 3D文档教程Apache SkyWalking 编译与校验实战指南Maven Profiles、Javadoc 与 Checkstyle 全解析Apache SkyWalking 编译与校验实战指南Maven Profiles、Javadoc 与 Checkstyle 全解析 本指南以 Apache文档教程CIFAR-ZOO性能大揭秘ResNeXt vs DenseNet谁才是图像分类王者CIFAR ZOO性能大揭秘ResNeXt vs DenseNet谁才是图像分类王者 CIFAR ZOO是一个专注于CIFAR数据集图像分类研究的开源项目上一篇Hono OpenAPI完全指南解锁自动生成API规范的终极技巧下一篇PhotoGIMP 快速安装指南免费让 GIMP 拥有 Photoshop 般的手感创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑