You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 04:00:47