PyQt5中QMdiSubWindow菜单创建、显隐及销毁的正确实现位置
PyQt5 MDI子窗口专属菜单实现方案
问题根源
此前方案出现无限递归的核心原因是选错了事件监听对象:
- 菜单本身是可获取输入焦点的控件,调用
setVisible(True)显示菜单时,焦点会被菜单抢占,立刻触发子窗口的focusOutEvent执行隐藏菜单逻辑 - 菜单隐藏后焦点又回到子窗口,再次触发
focusInEvent显示菜单,最终形成死循环 - 另外焦点事件是控件级的,哪怕只是点击主窗口工具栏、拖动窗口边框,都会触发子窗口焦点变化,根本无法准确判断子窗口是否为当前激活的MDI窗口。
正确实现逻辑
MDI场景下的子窗口菜单切换,Qt已经提供了专门的信号支持,不需要自己监听焦点事件:
- 监听对象:主窗口中
QMdiArea组件的subWindowActivated信号,该信号会在MDI区域当前激活子窗口变化时自动触发,参数为当前激活的子窗口实例(无激活窗口时参数为None),整个触发过程不会产生焦点抢占问题 - 显隐逻辑:信号触发时,先隐藏所有子窗口的专属菜单,再显示当前激活子窗口的专属菜单即可
- 销毁逻辑:重写子窗口的
closeEvent,在子窗口关闭时将专属菜单从主菜单栏移除,调用deleteLater()完成销毁,避免内存泄漏
注意:子窗口初始化时可以提前把专属菜单添加到主菜单栏,但默认设置为隐藏状态,不需要每次激活窗口时重复创建/销毁菜单,性能更好。
修正后的可运行代码
#!/usr/bin/python3 import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class TestSubWin(QMdiSubWindow): def __init__(self, parent=None): super().__init__(parent) self.setWidget(QLabel("Hello world")) self.main_window = parent # 创建子窗口专属菜单,添加到主菜单栏后默认隐藏 self.own_menu = QMenu("Sub win menu") self.main_window.menuBar().addMenu(self.own_menu) self.own_menu.setVisible(False) # 可以在这里给菜单添加专属动作 self.own_menu.addAction("Test action for sub window") def closeEvent(self, event): # 窗口关闭时移除并销毁专属菜单 self.main_window.menuBar().removeAction(self.own_menu.menuAction()) self.own_menu.deleteLater() super().closeEvent(event) class MainWindow(QMainWindow): def __init__(self, parent = None): super(MainWindow, self).__init__(parent) self.mdi = QMdiArea() self.setCentralWidget(self.mdi) bar = self.menuBar() file = bar.addMenu("File") file.addAction("New") file.triggered[QAction].connect(self.windowaction) # 绑定MDI子窗口激活信号 self.mdi.subWindowActivated.connect(self.on_subwindow_activated) self.setWindowTitle("MDI demo") def on_subwindow_activated(self, active_subwin): # 先隐藏所有子窗口的专属菜单 for subwin in self.mdi.subWindowList(): if hasattr(subwin, "own_menu"): subwin.own_menu.setVisible(False) # 显示当前激活子窗口的菜单 if active_subwin is not None and hasattr(active_subwin, "own_menu"): active_subwin.own_menu.setVisible(True) def windowaction(self, q): if q.text() == "New": sub = TestSubWin(self) self.mdi.addSubWindow(sub) sub.show() def main(): app = QApplication(sys.argv) ex = MainWindow() ex.show() sys.exit(app.exec_()) if __name__ == '__main__': main()
补充说明
这个实现是Qt官方MDI示例推荐的标准写法,除了菜单之外,子窗口专属的工具栏、状态栏组件切换,都可以绑定subWindowActivated信号实现,不会出现焦点冲突问题。
内容的提问来源于stack exchange,提问作者W.H.
相关产品推荐
相关产品推荐

