QMdiArea provides a Multiple Document Interface — a workspace area inside your main window where you can open, tile, and cascade multiple child windows. Each child window is a QMdiSubWindow. It's a useful pattern when your application needs to display several independent panels or documents side by side (think of an IDE with multiple open files, or a trading terminal with several chart windows).
However, QMdiArea has a few quirks that can trip you up, especially when you're designing your UI in Qt Designer. A common frustration: you close a subwindow, and it gets deleted, so you can't reopen it. Let's walk through how QMdiArea works, why this happens, and how to set things up so your subwindows behave the way you'd expect.
The problem with closing subwindows
When you add a widget to a QMdiArea using addSubWindow(), the MDI area wraps it in a QMdiSubWindow container. That container has a close button by default. When the user clicks that close button, the default behavior is to close and delete the child widget.
This means if you try to show it again later — for example, by calling .show() — the underlying widget is gone. Your reference to it now points to a deleted C++ object, which usually results in a crash or simply nothing happening.
This is the root cause of the behavior described in the question: subwindows designed in Qt Designer get instantiated as plain QWidget instances, wrapped automatically by addSubWindow(), and then destroyed when closed.
Preventing deletion on close
The fix is straightforward. You need to set the Qt.WA_DeleteOnClose widget attribute to False on the subwindow. This tells Qt: "When this window is closed, just hide it — don't destroy it."
Here's a minimal example that demonstrates the problem and the fix:
import sys
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QMdiArea,
QMdiSubWindow, QTextEdit, QAction
)
from PyQt6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QMdiArea Example")
self.resize(800, 600)
# Create the MDI area and set it as the central widget.
self.mdi_area = QMdiArea()
self.setCentralWidget(self.mdi_area)
# Create a subwindow with a text editor inside.
self.editor = QTextEdit()
self.editor.setPlainText("Hello from the subwindow!")
self.sub_window = self.mdi_area.addSubWindow(self.editor)
self.sub_window.setWindowTitle("My Editor")
# This is the fix — prevent deletion on close.
self.sub_window.setAttribute(Qt.WA_DeleteOnClose, False)
# Hide the subwindow initially.
self.sub_window.hide()
# Add a menu action to toggle the subwindow.
menu = self.menuBar().addMenu("&Windows")
show_action = QAction("Show Editor", self)
show_action.triggered.connect(self.toggle_editor)
menu.addAction(show_action)
def toggle_editor(self):
if self.sub_window.isVisible():
self.sub_window.hide()
else:
self.sub_window.show()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Run this, and you'll find you can close the subwindow with its close button, then reopen it from the Windows menu as many times as you like. Without the setAttribute(Qt.WA_DeleteOnClose, False) line, the subwindow would be destroyed the first time you closed it.
The Qt Designer wrinkle
When you design a QMdiArea in Qt Designer and add child widgets inside it, the generated UI code creates those children as plain QWidget instances — not as QMdiSubWindow instances. Qt Designer doesn't have full support for QMdiSubWindow as a designable container.
This means two things:
-
The widgets aren't properly registered as subwindows. Calling
self.mdi_area.subWindowList()may return an empty list because the widgets were placed as children in the designer but not added throughaddSubWindow(). -
When you do call
addSubWindow()yourself, the widget gets wrapped in aQMdiSubWindow— but that wrapper hasWA_DeleteOnCloseset toTrueby default. Closing it destroys your widget.
The practical solution is to not rely on Qt Designer for the subwindow arrangement inside the MDI area. Instead, design each subwindow's content as a separate widget (or even a separate .ui file), and then add them to the MDI area programmatically in your __init__ method.
Adding sub-windows in code
Here's a pattern that works well. You design your subwindow content as standalone custom widgets (either in code or in separate .ui files), and then wire everything up in your main window class:
import sys
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QMdiArea,
QMdiSubWindow, QTextEdit, QLabel,
QVBoxLayout, QWidget, QAction
)
from PyQt6.QtCore import Qt
class InfoPanel(QWidget):
"""A custom widget that will be placed inside a subwindow."""
def __init__(self):
super().__init__()
layout = QVBoxLayout()
layout.addWidget(QLabel("This is an info panel."))
layout.addWidget(QLabel("It shows some read-only data."))
self.setLayout(layout)
class EditorPanel(QWidget):
"""Another custom widget for a different subwindow."""
def __init__(self):
super().__init__()
layout = QVBoxLayout()
layout.addWidget(QLabel("Editor:"))
self.editor = QTextEdit()
layout.addWidget(self.editor)
self.setLayout(layout)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QMdiArea — Multiple Subwindows")
self.resize(900, 600)
self.mdi_area = QMdiArea()
self.setCentralWidget(self.mdi_area)
# Create subwindows programmatically.
self.info_subwindow = self._create_subwindow(
InfoPanel(), "Info Panel"
)
self.editor_subwindow = self._create_subwindow(
EditorPanel(), "Editor"
)
# Hide all subwindows initially.
self.info_subwindow.hide()
self.editor_subwindow.hide()
# Build a menu to show/hide them.
menu = self.menuBar().addMenu("&Windows")
info_action = QAction("Info Panel", self)
info_action.triggered.connect(
lambda: self._toggle_subwindow(self.info_subwindow)
)
menu.addAction(info_action)
editor_action = QAction("Editor", self)
editor_action.triggered.connect(
lambda: self._toggle_subwindow(self.editor_subwindow)
)
menu.addAction(editor_action)
def _create_subwindow(self, widget, title):
"""Add a widget to the MDI area as a persistent subwindow."""
sub_window = self.mdi_area.addSubWindow(widget)
sub_window.setWindowTitle(title)
sub_window.setAttribute(Qt.WA_DeleteOnClose, False)
return sub_window
def _toggle_subwindow(self, sub_window):
"""Show or hide a subwindow."""
if sub_window.isVisible():
sub_window.hide()
else:
sub_window.show()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
This gives you full control. Each subwindow is a proper QMdiSubWindow, registered with the MDI area, and safe to close and reopen.
Suppressing the automatic show on addSubWindow
One more gotcha: calling addSubWindow() can trigger the subwindow to become visible immediately. If you want all your subwindows hidden at startup, just call .hide() on each one right after adding it — as shown in the example above. The order matters: add first, then hide.
Alternatively, you can call addSubWindow() before the main window itself is shown. Since the parent isn't visible yet, the subwindows won't flash on screen.
Using .ui files for subwindow content
If you prefer to design your subwindow contents in Qt Designer, you absolutely can. Just design each panel as its own widget in a separate .ui file, load it using uic.loadUi() or compile it with pyuic5, and then pass the resulting widget to addSubWindow(). For a full walkthrough on designing widgets and dialogs with Qt Designer, see the Qt Designer GUI layout tutorial:
from PyQt6 import uic
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("MDI with .ui files")
self.resize(900, 600)
self.mdi_area = QMdiArea()
self.setCentralWidget(self.mdi_area)
# Load a widget designed in Qt Designer.
info_widget = uic.loadUi("info_panel.ui")
self.info_subwindow = self.mdi_area.addSubWindow(info_widget)
self.info_subwindow.setWindowTitle("Info Panel")
self.info_subwindow.setAttribute(Qt.WA_DeleteOnClose, False)
self.info_subwindow.hide()
This gives you the best of both worlds: visual design in Qt Designer for the content, and reliable programmatic control over the MDI subwindow lifecycle.
Summary
QMdiArea is a powerful component once you understand its expectations. The main thing is to take ownership of the subwindow lifecycle in your code rather than relying on Qt Designer to set it up for you. If you're new to building PyQt6 applications, you might find it helpful to start with creating your first window to get comfortable with the fundamentals before working with more advanced components like QMdiArea.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PyQt6 Edition) The hands-on guide to making apps with Python — Save time and build better with this book. Over 15K copies sold.