How is QMdiArea supposed to be used?

Working with QMdiArea and QMdiSubWindow in PyQt6, including how to handle subwindow closing and visibility
Heads up! You've already completed this tutorial.

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:

python
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.

The complete guide to packaging Python GUI applications with PyInstaller.

This means two things:

  1. 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 through addSubWindow().

  2. When you do call addSubWindow() yourself, the widget gets wrapped in a QMdiSubWindow — but that wrapper has WA_DeleteOnClose set to True by 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:

python
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:

python
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.

Get the book

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

How is QMdiArea supposed to be used? was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.