PyQt6中QWidget无法彻底删除致鼠标中键回调报错的解决问询
问题根源
你遇到的报错核心原因是第三方mouse库的全局事件绑定机制:它会持有回调函数的引用,哪怕Qt已经销毁了Widget对象,这个引用依然存在。当中键点击时,mouse库还是会调用已销毁对象的test_function,导致访问无效的C++对象抛出RuntimeError。另外原代码里主窗口的中键回调只是通过判断test_widget是否存在跳过执行,并没有真正移除绑定,关闭Widget后两个回调会同时触发。
解决方案
方案一:改用Qt原生鼠标事件处理(推荐)
Qt自身的事件系统会自动管理对象生命周期,当Widget被销毁后,不会再分发事件给它,完全避免这类问题。
修改main_window.py
from PyQt6.QtWidgets import ( QMainWindow, QPushButton, QWidget, ) from PyQt6.QtCore import Qt from test_widget import TestWidget class MainWindow(QMainWindow): def __init__(self) -> None: super().__init__() self.button = QPushButton("Open Widget", self) self.test_widget = None self.button.clicked.connect(self.open_widget) def open_widget(self) -> None: self.test_widget = TestWidget(self) self.test_widget.show() def close_widget(self, widget: QWidget) -> None: widget.deleteLater() self.test_widget = None def mousePressEvent(self, event): # 处理鼠标中键点击 if event.button() == Qt.MouseButton.MiddleButton: if self.test_widget is not None: return print("Main Window") # 调用父类方法处理其他事件 super().mousePressEvent(event)
修改test_widget.py
from PyQt6.QtWidgets import ( QLineEdit, QWidget, ) from PyQt6.QtCore import Qt class TestWidget(QWidget): def __init__(self, main_window) -> None: super().__init__() self.main_window = main_window self.line_edit = QLineEdit(self) def closeEvent(self, _) -> None: self.main_window.close_widget(self) def mousePressEvent(self, event): if event.button() == Qt.MouseButton.MiddleButton: print(self.line_edit.text()) super().mousePressEvent(event)
方案二:继续使用mouse库,但手动解绑回调
如果必须依赖第三方mouse库,需要在Widget销毁前手动移除对应的回调,同时管理主窗口回调的绑定状态:
修改main_window.py
import mouse from PyQt6.QtWidgets import ( QMainWindow, QPushButton, QWidget, ) from test_widget import TestWidget class MainWindow(QMainWindow): def __init__(self) -> None: super().__init__() self.button = QPushButton("Open Widget", self) self.test_widget = None self.button.clicked.connect(self.open_widget) # 保存主窗口回调引用,方便后续解绑 self.main_middle_callback = self.test_function mouse.on_middle_click(self.main_middle_callback) def open_widget(self) -> None: # 打开Widget时暂时解绑主窗口中键回调 mouse.unhook(self.main_middle_callback) self.test_widget = TestWidget(self) self.test_widget.show() def close_widget(self, widget: QWidget) -> None: # 先让Widget解绑自身回调 widget.unhook_mouse_callback() widget.deleteLater() self.test_widget = None # 恢复主窗口中键回调 mouse.on_middle_click(self.main_middle_callback) def test_function(self) -> None: print("Main Window")
修改test_widget.py
import mouse from PyQt6.QtWidgets import ( QLineEdit, QWidget, ) class TestWidget(QWidget): def __init__(self, main_window) -> None: super().__init__() self.main_window = main_window self.line_edit = QLineEdit(self) # 保存自身回调引用 self.widget_middle_callback = self.test_function mouse.on_middle_click(self.widget_middle_callback) def closeEvent(self, _) -> None: self.main_window.close_widget(self) def unhook_mouse_callback(self): # 解绑自身中键回调,清除mouse库的引用 mouse.unhook(self.widget_middle_callback) def test_function(self) -> None: print(self.line_edit.text())
关键说明
mouse.unhook()需要传入绑定的回调函数引用,因此必须提前保存该引用,确保能精准移除绑定- 打开Widget时解绑主窗口回调、关闭后恢复,避免两个回调同时触发
- Widget销毁前必须先解绑自身回调,确保
mouse库不再持有对它的引用
内容的提问来源于stack exchange,提问作者CodePidgy
相关产品推荐
相关产品推荐

