PyQt5事件处理:重写鼠标/键盘/窗口事件函数(附绘图实战)

寒烟似雪
4天前发布 /正在检测是否收录...

第13篇:PyQt5事件处理:重写事件函数实现灵活交互(完整代码)

哈喽~ 欢迎来到PyQt5系列的第13篇!前面我们学了信号与槽自定义信号,这两种方式能解决大部分交互需求,但它们是基于控件预设的“高级通知”(比如按钮点击、输入框文本变化)。而在实际开发中,我们还需要捕获更底层的用户操作——比如鼠标点击的具体位置、键盘按下的快捷键、窗口缩放的实时大小等,这就需要用到 PyQt5事件处理
mk9obcxb.png

今天我们就来学习如何通过重写事件函数,捕获并处理鼠标、键盘、窗口等底层事件,实现更灵活的界面交互逻辑,全程搭配完整可运行代码,新手也能轻松掌握!

一、先搞懂:事件与信号的区别(核心概念)

很多同学会混淆事件(Event)信号(Signal),其实它们是两个不同层级的概念,核心区别如下:

对比维度事件(Event)信号(Signal)
本质底层的输入/系统消息(如鼠标移动、键盘按下、窗口缩放)控件发出的高级通知(如按钮点击、文本变化)
触发源操作系统/用户直接操作(如鼠标点一下、按键盘)控件状态变化(如按钮被点击后发出clicked信号)
处理方式重写控件的事件函数(如mousePressEvent绑定信号到槽函数(如btn.clicked.connect(func)
灵活性极高,可捕获最细节的操作(如鼠标坐标、按键类型)中等,只能响应控件预设的信号
关系信号是基于事件封装的(比如按钮的clicked信号,底层就是鼠标点击事件)-

举个例子:点击按钮时,操作系统会先发送鼠标点击事件给按钮,按钮接收到事件后,会触发clicked信号,最终执行我们绑定的槽函数。

二、事件处理的核心:重写事件函数

PyQt5中所有控件都继承自QWidget,而QWidget内置了大量事件函数(如mousePressEventkeyPressEvent)。我们只需在自定义控件/窗口类中重写这些函数,就能捕获并处理对应的事件。

核心规则

  1. 事件函数是固定名称的(比如处理鼠标点击的函数必须叫mousePressEvent),不能自定义名称;
  2. 事件函数的参数固定,第一个参数是事件对象(如QMouseEventQKeyEvent),包含事件的详细信息;
  3. 重写事件函数时,若需要保留控件的原有行为,需调用父类的同名事件函数(如super().mousePressEvent(event))。

三、实战1:鼠标事件处理(最常用)

鼠标事件是最常见的底层事件,包括按下、释放、双击、移动四种,对应的事件函数如下:

事件函数作用事件对象核心方法
mousePressEvent(event)鼠标按下时触发QMouseEventevent.pos():获取鼠标位置;event.button():判断鼠标键(左/右/中)
mouseReleaseEvent(event)鼠标释放时触发QMouseEvent同上
mouseDoubleClickEvent(event)鼠标双击时触发QMouseEvent同上
mouseMoveEvent(event)鼠标移动时触发QMouseEvent同上

完整代码:鼠标事件捕获演示

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont

class MouseEventDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("鼠标事件处理演示")
        self.resize(500, 400)
        self.setMouseTracking(True)  # 关键:开启鼠标追踪(不按下也能捕获移动事件)

        # 布局与标签(显示事件信息)
        layout = QVBoxLayout()
        self.info_label = QLabel("鼠标状态:未操作\n位置:(0, 0)\n按键:无", alignment=Qt.AlignCenter)
        self.info_label.setFont(QFont("微软雅黑", 12))
        layout.addWidget(self.info_label)
        self.setLayout(layout)

    # ---------- 1. 鼠标按下事件 ----------
    def mousePressEvent(self, event):
        # 获取鼠标位置(相对于当前窗口)
        x = event.x()
        y = event.y()
        # 判断按下的鼠标键
        if event.button() == Qt.LeftButton:
            btn_text = "左键"
        elif event.button() == Qt.RightButton:
            btn_text = "右键"
        elif event.button() == Qt.MiddleButton:
            btn_text = "中键"
        else:
            btn_text = "未知"
        # 更新标签信息
        self.info_label.setText(f"鼠标状态:按下\n位置:(, {y})\n按键:{btn_text}")
        # 保留父类的原有行为(可选,比如让窗口能被拖动)
        super().mousePressEvent(event)

    # ---------- 2. 鼠标释放事件 ----------
    def mouseReleaseEvent(self, event):
        x = event.x()
        y = event.y()
        self.info_label.setText(f"鼠标状态:释放\n位置:(, {y})\n按键:无")
        super().mouseReleaseEvent(event)

    # ---------- 3. 鼠标双击事件 ----------
    def mouseDoubleClickEvent(self, event):
        x = event.x()
        y = event.y()
        self.info_label.setText(f"鼠标状态:双击\n位置:(, {y})\n按键:左键")
        # 双击窗口标题栏可以最大化,这里保留该行为
        super().mouseDoubleClickEvent(event)

    # ---------- 4. 鼠标移动事件 ----------
    def mouseMoveEvent(self, event):
        x = event.x()
        y = event.y()
        # 实时显示鼠标位置(需开启setMouseTracking(True))
        self.info_label.setText(f"鼠标状态:移动\n位置:(, {y})\n按键:无")
        super().mouseMoveEvent(event)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MouseEventDemo()
    window.show()
    sys.exit(app.exec_())

关键要点

  1. 鼠标追踪:默认情况下,mouseMoveEvent只有在鼠标按下时才会触发。调用self.setMouseTracking(True)后,不按下鼠标也能捕获移动事件
  2. 鼠标位置event.x()/event.y()获取的是相对于当前窗口的坐标event.globalX()/event.globalY()获取的是相对于屏幕的坐标
  3. 鼠标键判断:通过event.button()配合Qt.LeftButton/Qt.RightButton/Qt.MiddleButton判断按下的按键。

四、实战2:键盘事件处理(快捷键实现)

键盘事件用于捕获按键操作,比如实现快捷键(如Ctrl+S保存、ESC关闭窗口),核心事件函数是keyPressEventkeyReleaseEvent

事件函数作用事件对象核心方法
keyPressEvent(event)按键按下时触发QKeyEventevent.key():获取按键;event.modifiers():判断组合键(如Ctrl/Shift)
keyReleaseEvent(event)按键释放时触发QKeyEvent同上

完整代码:键盘事件与快捷键实现

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QMessageBox
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont

class KeyEventDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("键盘事件与快捷键演示")
        self.resize(400, 300)

        layout = QVBoxLayout()
        self.info_label = QLabel("""
            快捷键说明:
            1. 按ESC键:关闭窗口
            2. 按Ctrl+S:触发保存提示
            3. 按方向键:移动标签位置
        """, alignment=Qt.AlignCenter)
        self.info_label.setFont(QFont("微软雅黑", 11))
        # 可移动的标签
        self.move_label = QLabel("我是可移动的标签", alignment=Qt.AlignCenter)
        self.move_label.setStyleSheet("color: #e74c3c; font-size: 14px;")
        self.move_label.setFixedSize(150, 30)

        layout.addWidget(self.info_label)
        layout.addWidget(self.move_label)
        self.setLayout(layout)

        # 让窗口获得焦点,否则无法捕获键盘事件
        self.setFocusPolicy(Qt.StrongFocus)

    # ---------- 键盘按下事件 ----------
    def keyPressEvent(self, event):
        # 获取当前标签位置
        x = self.move_label.x()
        y = self.move_label.y()

        # 1. 处理方向键:移动标签
        if event.key() == Qt.Key_Left:
            self.move_label.move(x - 10, y)
        elif event.key() == Qt.Key_Right:
            self.move_label.move(x + 10, y)
        elif event.key() == Qt.Key_Up:
            self.move_label.move(x, y - 10)
        elif event.key() == Qt.Key_Down:
            self.move_label.move(x, y + 10)

        # 2. 处理ESC键:关闭窗口
        elif event.key() == Qt.Key_Escape:
            self.close()

        # 3. 处理组合键:Ctrl+S
        elif event.key() == Qt.Key_S and event.modifiers() == Qt.ControlModifier:
            QMessageBox.information(self, "快捷键触发", "Ctrl+S:模拟保存成功!")

        # 保留父类的原有行为
        super().keyPressEvent(event)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = KeyEventDemo()
    window.show()
    sys.exit(app.exec_())

关键要点

  1. 焦点问题:控件必须获得焦点才能捕获键盘事件。调用self.setFocusPolicy(Qt.StrongFocus)让窗口强制获得焦点;
  2. 组合键判断:通过event.modifiers()判断组合键,常用的有Qt.ControlModifier(Ctrl键)、Qt.ShiftModifier(Shift键)、Qt.AltModifier(Alt键);
  3. 常用按键常量Qt.Key_Escape(ESC)、Qt.Key_Enter(回车)、Qt.Key_A-Qt.Key_Z(字母键)。

五、实战3:窗口事件处理(关闭/缩放/移动)

窗口事件用于捕获窗口的状态变化,比如关闭、缩放、移动,最常用的是closeEvent(关闭窗口时触发,用于确认是否关闭)。

事件函数作用事件对象核心方法
closeEvent(event)窗口关闭时触发QCloseEventevent.accept():允许关闭;event.ignore():阻止关闭
resizeEvent(event)窗口缩放时触发QResizeEventevent.size():获取新大小;event.oldSize():获取旧大小
moveEvent(event)窗口移动时触发QMoveEventevent.pos():获取新位置;event.oldPos():获取旧位置

完整代码:窗口事件处理演示

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QMessageBox
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont

class WindowEventDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("窗口事件处理演示")
        self.resize(400, 300)

        layout = QVBoxLayout()
        self.info_label = QLabel("窗口状态:正常\n大小:400×300\n位置:(0, 0)", alignment=Qt.AlignCenter)
        self.info_label.setFont(QFont("微软雅黑", 12))
        layout.addWidget(self.info_label)
        self.setLayout(layout)

    # ---------- 1. 窗口关闭事件(最常用) ----------
    def closeEvent(self, event):
        # 弹出确认框,询问是否关闭
        reply = QMessageBox.question(
            self,
            "关闭确认",
            "确定要关闭窗口吗?未保存的内容可能会丢失!",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No
        )
        if reply == QMessageBox.Yes:
            event.accept()  # 允许关闭窗口
        else:
            event.ignore()  # 阻止关闭窗口

    # ---------- 2. 窗口缩放事件 ----------
    def resizeEvent(self, event):
        # 获取窗口新大小
        new_size = event.size()
        width = new_size.width()
        height = new_size.height()
        # 更新状态信息
        self.info_label.setText(f"窗口状态:缩放\n大小:{width}×{height}\n位置:({self.x()}, {self.y()})")
        super().resizeEvent(event)

    # ---------- 3. 窗口移动事件 ----------
    def moveEvent(self, event):
        # 获取窗口新位置
        new_pos = event.pos()
        x = new_pos.x()
        y = new_pos.y()
        # 更新状态信息
        self.info_label.setText(f"窗口状态:移动\n大小:{self.width()}×{self.height()}\n位置:(, {y})")
        super().moveEvent(event)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = WindowEventDemo()
    window.show()
    sys.exit(app.exec_())

关键要点

  1. 关闭事件控制closeEvent中,event.accept()允许窗口关闭,event.ignore()阻止关闭。这是实现“关闭确认”的核心方法;
  2. 大小/位置获取resizeEvent中用event.size()获取新大小,moveEvent中用event.pos()获取新位置;
  3. 父类方法调用:重写窗口事件时,必须调用父类的同名方法,否则窗口的正常功能会被破坏(比如无法缩放、移动)。

六、综合案例:简易绘图工具(鼠标事件实战)

结合鼠标的按下、移动、释放事件,实现一个简易的绘图工具——按下鼠标拖动时绘制线条,释放鼠标时结束绘制。

完整代码:简易绘图工具

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPainter, QPen

class PaintTool(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()
        # 绘图相关变量
        self.is_drawing = False  # 是否正在绘图
        self.start_point = QPoint()  # 绘图起始点
        self.end_point = QPoint()  # 绘图结束点

    def init_ui(self):
        self.setWindowTitle("简易绘图工具(鼠标拖动绘制线条)")
        self.resize(600, 500)
        self.setMouseTracking(True)

    # ---------- 鼠标按下:开始绘图 ----------
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.is_drawing = True
            self.start_point = event.pos()  # 记录起始点
            self.end_point = self.start_point  # 初始结束点等于起始点
        super().mousePressEvent(event)

    # ---------- 鼠标移动:更新绘图 ----------
    def mouseMoveEvent(self, event):
        if self.is_drawing:
            self.end_point = event.pos()  # 更新结束点
            self.update()  # 触发重绘(调用paintEvent)
        super().mouseMoveEvent(event)

    # ---------- 鼠标释放:结束绘图 ----------
    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.is_drawing = False
            self.end_point = event.pos()
            self.update()  # 绘制最后一条线
        super().mouseReleaseEvent(event)

    # ---------- 重绘事件:实际绘制线条 ----------
    def paintEvent(self, event):
        # QPainter是PyQt5的绘图工具
        painter = QPainter(self)
        # 设置画笔样式(颜色、宽度、线条类型)
        pen = QPen(Qt.red, 3, Qt.SolidLine)
        painter.setPen(pen)
        # 绘制线条:从起始点到结束点
        painter.drawLine(self.start_point, self.end_point)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = PaintTool()
    window.show()
    sys.exit(app.exec_())

核心逻辑

  1. 绘图状态控制:用is_drawing标记是否正在绘图,鼠标按下时设为True,释放时设为False
  2. 重绘触发self.update()会强制触发paintEvent方法,实现实时绘图;
  3. QPainter绘图QPainter是PyQt5的绘图核心类,drawLine方法用于绘制线条,还支持绘制矩形、圆形等图形。

七、常见问题排查

1. 鼠标移动事件不触发

  • 问题原因:未开启鼠标追踪,默认只有鼠标按下时才会触发mouseMoveEvent
  • 解决方案:调用self.setMouseTracking(True)开启鼠标追踪。

2. 键盘事件不响应

  • 问题原因1:控件没有获得焦点;

    • 解决方案:调用self.setFocusPolicy(Qt.StrongFocus)让控件获得焦点;
  • 问题原因2:重写事件函数时忘记调用父类方法;

    • 解决方案:在函数末尾添加super().keyPressEvent(event)

3. 窗口无法关闭/缩放

  • 问题原因:重写closeEvent/resizeEvent时忘记调用父类方法,破坏了窗口的原有功能;
  • 解决方案:必须调用super().closeEvent(event)super().resizeEvent(event)

4. 绘图时线条闪烁

  • 问题原因:每次update()都会重绘整个窗口,导致闪烁;
  • 解决方案:开启双缓冲绘图(PyQt5默认开启,无需手动设置),或只重绘需要更新的区域。

总结

  1. 事件与信号的区别:事件是底层操作,信号是控件的高级通知,信号基于事件封装;
  2. 事件处理核心:重写固定名称的事件函数,调用父类方法保留原有功能;
  3. 常用事件

    • 鼠标事件:mousePressEvent/mouseMoveEvent(需开启追踪);
    • 键盘事件:keyPressEvent(需获得焦点);
    • 窗口事件:closeEvent(用于关闭确认);
  4. 实战价值:事件处理能实现信号与槽无法覆盖的交互(如绘图、快捷键、窗口状态监控)。

下一章我们将学习PyQt5多线程编程——解决耗时操作导致界面卡顿的问题,这是开发高性能GUI程序的必备技能!如果在事件处理中遇到绘图、快捷键实现的问题,欢迎在评论区留言讨论~

© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏
评论 抢沙发
OωO
取消
SSL