Python3.8+PySide2:QMainWindow实例closeEvent内置方法替换失效问题
替换QMainWindow实例closeEvent方法的差异与解决方案
问题背景
尝试替换QMainWindow类实例的内置closeEvent方法时,出现两种代码表现不一致的情况:
CODE #1(不生效)
import sys from PySide2.QtUiTools import QUiLoader from PySide2.QtWidgets import QApplication from PySide2.QtCore import QFile, QIODevice app = QApplication(sys.argv) ui_file_name = "ui\Main.ui" ui_file = QFile(ui_file_name) if not ui_file.open(QIODevice.ReadOnly): print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString())) sys.exit(-1) loader = QUiLoader() window = loader.load(ui_file) ui_file.close() if not window: print(loader.errorString()) sys.exit(-1) def MainFormCloseEvent(event): print(event) event.ignore() print(window.closeEvent) window.closeEvent=MainFormCloseEvent print(window.closeEvent) window.show() sys.exit(app.exec_())
运行输出:
<built-in method closeEvent of PySide2.QtWidgets.QMainWindow object at 0x000000000573BF80> <function MainFormCloseEvent at 0x0000000002C37430>
窗体关闭时不会调用MainFormCloseEvent。
CODE #2(正常生效)
import sys from PySide2.QtUiTools import QUiLoader from PySide2.QtWidgets import QApplication from PySide2.QtCore import QFile, QIODevice from PySide2.QtWidgets import QMainWindow def MainFormCloseEvent(event): print(event) event.ignore() class MainWindow(QMainWindow): def __init__(self): super().__init__() def closeEvent(self, event): print('Original class method') app = QApplication(sys.argv) window = MainWindow() print(window.closeEvent) window.closeEvent=MainFormCloseEvent print(window.closeEvent) window.show() sys.exit(app.exec_())
运行输出:
<bound method MainWindow.closeEvent of <__main__.MainWindow(0x51522b0) at 0x0000000004E8AF40>> <function MainFormCloseEvent at 0x0000000002C37430> <PySide2.QtGui.QCloseEvent object at 0x0000000004E8F340>
窗体关闭时正常调用MainFormCloseEvent。
根本差异解析
两种情况的核心区别在于closeEvent方法的底层实现:
- CODE #1中的
closeEvent是C++内置方法:通过QUiLoader加载的是Qt原生的QMainWindow实例,其closeEvent是C层面实现的内置方法。Qt的事件分发系统会直接调用C方法,不会触发Python层面的实例属性查找,因此Python赋值的新方法无法被调用。 - CODE #2中的
closeEvent是Python绑定方法:自定义的MainWindow类继承自QMainWindow,并重写了closeEvent,此时该方法是Python层面的绑定方法。Qt的元对象系统会将事件转发到Python的方法实现,Python的实例属性优先级高于类方法,因此直接赋值实例的closeEvent可以覆盖原方法。
让CODE #1生效的解决方案
方案1:使用事件过滤器拦截CloseEvent
通过给窗口安装事件过滤器,直接拦截CloseEvent事件:
import sys from PySide2.QtUiTools import QUiLoader from PySide2.QtWidgets import QApplication from PySide2.QtCore import QFile, QIODevice, QObject, QEvent app = QApplication(sys.argv) ui_file_name = "ui\Main.ui" ui_file = QFile(ui_file_name) if not ui_file.open(QIODevice.ReadOnly): print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString())) sys.exit(-1) loader = QUiLoader() window = loader.load(ui_file) ui_file.close() if not window: print(loader.errorString()) sys.exit(-1) class CloseEventFilter(QObject): def eventFilter(self, obj, event): if obj == window and event.type() == QEvent.Close: print(event) event.ignore() return True # 返回True表示事件已处理,不再传递 return super().eventFilter(obj, event) # 安装事件过滤器 event_filter = CloseEventFilter() window.installEventFilter(event_filter) window.show() sys.exit(app.exec_())
方案2:继承QMainWindow并加载UI到自定义类
自定义QMainWindow子类,将UI文件加载到子类实例中,这样可以直接重写closeEvent:
import sys from PySide2.QtUiTools import QUiLoader from PySide2.QtWidgets import QApplication, QMainWindow from PySide2.QtCore import QFile, QIODevice class CustomMainWindow(QMainWindow): def closeEvent(self, event): print(event) event.ignore() app = QApplication(sys.argv) ui_file_name = "ui\Main.ui" ui_file = QFile(ui_file_name) if not ui_file.open(QIODevice.ReadOnly): print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString())) sys.exit(-1) loader = QUiLoader() window = CustomMainWindow() # 将加载的UI设置为窗口的中央部件 ui_widget = loader.load(ui_file, window) window.setCentralWidget(ui_widget) ui_file.close() if not ui_widget: print(loader.errorString()) sys.exit(-1) window.show() sys.exit(app.exec_())
内容的提问来源于stack exchange,提问作者Nikolas
相关产品推荐
相关产品推荐

