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

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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 03:18:24