If you've built a PyQt6 application you might want a File → New menu action that opens a fresh, blank copy of the same form. This is a common pattern in desktop applications: each "New" click spawns another independent window.
The Qt documentation covers QFileDialog for opening and saving files, but creating a new instance of your own window is a different problem entirely. It has nothing to do with file dialogs — it's about creating and managing multiple windows in your application.
Let's walk through how to do this in PyQt6.
Why windows disappear: the garbage collection problem
The most common mistake when opening new windows in PyQt6 looks something like this:
def new_window(self):
w = MainWindow()
w.show()
You click File → New, a window flashes on screen, and then immediately vanishes. What happened?
The variable w is local to the new_window method. As soon as the method finishes executing, Python's garbage collector sees that nothing references w anymore and destroys it — taking the window with it.
To keep a window alive, you need to store a reference to it somewhere persistent. The simplest approach is to keep a list of all open windows.
Keeping track of open windows
Here's the basic pattern. We store each new window in a list on the parent window, so Python always has a reference to it:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Character Sheet")
# Keep references to any windows we open
self.windows = []
# Set up the File menu
menu = self.menuBar()
file_menu = menu.addMenu("&File")
new_action = QAction("&New Character", self)
new_action.triggered.connect(self.new_window)
file_menu.addAction(new_action)
def new_window(self):
w = MainWindow()
w.show()
self.windows.append(w)
Each time you click File → New Character, a new MainWindow is created, shown, and appended to the self.windows list. Because the list holds a reference, the garbage collector leaves the window alone.
This works, but there's a subtle issue: the child windows are tracked by the parent window that created them. If you close the parent, all its tracked children could be garbage collected too. For a simple application this is often fine, but for something more robust, you can track windows at the module or application level instead.
A shared window list
A cleaner approach is to maintain a single, shared list of all open windows. This way, every window is independent — closing one doesn't affect the others:
from PyQt6.QtWidgets import QApplication, QMainWindow, QAction, QLabel
from PyQt6.QtCore import Qt
import sys
# Module-level list to track all open windows
active_windows = []
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Character Sheet")
self.setMinimumSize(400, 300)
label = QLabel("New Character Sheet")
label.setAlignment(Qt.AlignCenter)
self.setCentralWidget(label)
# Set up the File menu
menu = self.menuBar()
file_menu = menu.addMenu("&File")
new_action = QAction("&New Character", self)
new_action.setShortcut("Ctrl+N")
new_action.triggered.connect(self.new_window)
file_menu.addAction(new_action)
def new_window(self):
w = MainWindow()
w.show()
active_windows.append(w)
app = QApplication(sys.argv)
w = MainWindow()
w.show()
active_windows.append(w) # Don't forget to track the first window too!
sys.exit(app.exec())
Now every window lives in the active_windows list. You can open a new character sheet from any window, and they all stay alive independently.
Cleaning up closed windows
One thing to be aware of: as windows are closed, they remain in the active_windows list, consuming memory. For an application where you might open and close many character sheets, it's good practice to remove windows from the list when they're closed.
You can do this by overriding the closeEvent method on your window:
def closeEvent(self, event):
# Remove this window from the tracking list
if self in active_windows:
active_windows.remove(self)
event.accept()
This keeps the list tidy and lets Python clean up the window's memory after it's closed.
Using a Qt Designer .ui file
If your character sheet is designed in Qt Designer, the approach is exactly the same. You just load the .ui file in your window's __init__ method. There are two ways to do this — let's look at both. For more on designing interfaces with Qt Designer, see our Qt Designer GUI layout tutorial.
Loading with uic.loadUi
The uic.loadUi function loads a .ui file and applies it directly to your window:
from PyQt6 import uic
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("charactersheet.ui", self)
# Connect the menu action (assuming you added it in Designer)
self.actionNew_Character.triggered.connect(self.new_window)
def new_window(self):
w = MainWindow()
w.show()
active_windows.append(w)
Each new MainWindow() instance loads a fresh copy of the .ui file, giving you a blank character sheet every time.
Using a converted Python file
If you've converted your .ui file to Python using pyuic5:
pyuic5 charactersheet.ui -o charactersheet_ui.py
You can then import and use the generated class:
from charactersheet_ui import Ui_MainWindow
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.actionNew_Character.triggered.connect(self.new_window)
def new_window(self):
w = MainWindow()
w.show()
active_windows.append(w)
Both approaches work identically — each call to MainWindow() creates a completely independent window with its own widgets and state.
Complete working example
Here's a full, self-contained example that puts everything together. It creates a simple "character sheet" window with a few input fields, a File → New Character action, and proper cleanup when windows are closed. The menu is built using actions, toolbars, and menus:
import sys
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QAction, QWidget,
QVBoxLayout, QFormLayout, QLineEdit, QSpinBox, QLabel
)
from PyQt6.QtCore import Qt
# Shared list to track all open windows
active_windows = []
class CharacterSheet(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Character Sheet")
self.setMinimumSize(350, 300)
# --- Menu bar ---
menu = self.menuBar()
file_menu = menu.addMenu("&File")
new_action = QAction("&New Character", self)
new_action.setShortcut("Ctrl+N")
new_action.setStatusTip("Create a new character sheet")
new_action.triggered.connect(self.new_character)
file_menu.addAction(new_action)
close_action = QAction("&Close", self)
close_action.setShortcut("Ctrl+W")
close_action.triggered.connect(self.close)
file_menu.addAction(close_action)
# --- Central widget with a form ---
central = QWidget()
layout = QVBoxLayout()
central.setLayout(layout)
title = QLabel("RPG Character Sheet")
title.setAlignment(Qt.AlignCenter)
title.setStyleSheet("font-size: 18px; font-weight: bold; margin: 10px;")
layout.addWidget(title)
form = QFormLayout()
form.addRow("Character Name:", QLineEdit())
form.addRow("Class:", QLineEdit())
form.addRow("Race:", QLineEdit())
form.addRow("Level:", QSpinBox())
form.addRow("Strength:", QSpinBox())
form.addRow("Dexterity:", QSpinBox())
form.addRow("Constitution:", QSpinBox())
form.addRow("Intelligence:", QSpinBox())
form.addRow("Wisdom:", QSpinBox())
form.addRow("Charisma:", QSpinBox())
layout.addLayout(form)
self.setCentralWidget(central)
self.statusBar().showMessage("Ready")
def new_character(self):
"""Create and show a new character sheet window."""
w = CharacterSheet()
w.show()
active_windows.append(w)
def closeEvent(self, event):
"""Remove this window from the tracking list when closed."""
if self in active_windows:
active_windows.remove(self)
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
# Create and show the first window
window = CharacterSheet()
window.show()
active_windows.append(window)
sys.exit(app.exec())
Run this and click File → New Character (or press Ctrl+N) to open as many character sheets as you need. Each window is completely independent — you can fill in different character details in each one, and closing one window doesn't affect the others.
Summary
Opening a new instance of your own window in PyQt6 comes down to two things:
- Create a new instance of your window class — just call the constructor again, e.g.
MainWindow(). - Keep a reference to it so Python's garbage collector doesn't destroy it immediately.
A module-level list is a straightforward way to manage this. Combined with a closeEvent override to clean up closed windows, you have a reliable pattern that works whether your UI is built in code or loaded from a Qt Designer .ui file. If you're new to building PyQt6 interfaces, start with our guide to creating your first window.
PyQt/PySide 1:1 Coaching with Martin Fitzpatrick
Save yourself time and frustration. Get one on one help with your Python GUI projects. Working together with you I'll identify issues and suggest fixes, from bugs and usability to architecture and maintainability.