Opening a subwindow from a button event

How to open a second window from a button click using PyQt6 and Qt Designer .ui files
Heads up! You've already completed this tutorial.

How do I open a subwindow from a button click event? I know how to load a .ui file made in Qt Designer, but I want to click a button in my main window and have it open a second window — ideally loaded from a different .ui file.

This is a very common pattern in GUI applications: you have a main window, and when the user clicks a button, a second window (often a dialog) pops up. If you're using Qt Designer to design your interfaces, you'll want to load separate .ui files for each window.

Let's walk through how to set this up in PyQt6, step by step.

The overall approach

Here's the plan:

  1. Create two .ui files in Qt Designer — one for your main window and one for your dialog (or sub-window).
  2. Load both .ui files in your Python code.
  3. Connect a button's clicked signal in the main window to a method that opens the second window.

Creating the .ui files

In Qt Designer, create two files:

  • main_window.ui — A QMainWindow with at least one QPushButton. Give the button an object name you'll remember, such as pushButton_open.
  • dialog.ui — A QDialog with whatever content you'd like to show in the sub-window.

If you're not familiar with Qt Designer, our Qt Designer guide walks you through the basics.

Loading .ui files with uic.loadUiType

PyQt6 provides uic.loadUiType() which reads a .ui file and returns two things: a form class and a base class. You can use these to build your window classes by inheriting from both.

python
from PyQt6 import uic

WindowForm, WindowBase = uic.loadUiType("main_window.ui")
DialogForm, DialogBase = uic.loadUiType("dialog.ui")

WindowForm contains all the widgets and layout you designed in Qt Designer. WindowBase is the corresponding Qt class (e.g., QMainWindow or QDialog). By inheriting from both, your class gets the full Designer layout and all the standard Qt behavior.

Building the dialog class

The dialog class inherits from both the base class and the form class, then calls setupUi(self) to load the layout:

python
class Dialog(DialogBase, DialogForm):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

That's all you need for a basic dialog. The parent=None parameter is optional but useful — passing the parent window helps Qt manage the dialog's lifetime and positioning.

Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

Get the book

Building the main window class

The main window class follows the same pattern. The difference is that here we also connect the button's clicked signal to a method that opens the dialog:

python
class MainWindow(WindowBase, WindowForm):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.pushButton_open.clicked.connect(self.show_dialog)

    def show_dialog(self):
        dialog = Dialog(self)
        dialog.exec()

When the user clicks the button named pushButton_open, the show_dialog method runs. It creates a new Dialog instance and calls dialog.exec(), which opens the dialog as a modal window — meaning the user must close it before interacting with the main window again.

If you'd prefer the dialog to be non-modal (so the user can interact with both windows at the same time), use dialog.show() instead of dialog.exec(). In that case, you'll want to store a reference to the dialog so it doesn't get garbage collected immediately:

python
def show_dialog(self):
    self.dialog = Dialog(self)
    self.dialog.show()

For more on the differences, see our tutorial on PyQt6 dialogs.

Complete working example

Here's the full code, putting everything together. Update the file paths to point to your own .ui files:

python
import sys

from PyQt6 import uic
from PyQt6.QtWidgets import QApplication

# Load the .ui files. These return (FormClass, BaseClass) tuples.
# Update these paths to match your .ui file locations.
WindowForm, WindowBase = uic.loadUiType("main_window.ui")
DialogForm, DialogBase = uic.loadUiType("dialog.ui")


class Dialog(DialogBase, DialogForm):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)


class MainWindow(WindowBase, WindowForm):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        # Connect the button to the method that opens the dialog.
        # Replace 'pushButton_open' with the object name of your button.
        self.pushButton_open.clicked.connect(self.show_dialog)

    def show_dialog(self):
        dialog = Dialog(self)
        dialog.exec()


def main():
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())


if __name__ == "__main__":
    main()

Without .ui files

If you don't want to use Qt Designer at all, you can build the same thing entirely in code. This is helpful for understanding what's happening behind the scenes:

python
import sys

from PyQt6.QtWidgets import (
    QApplication,
    QDialog,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)


class Dialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("My Dialog")

        layout = QVBoxLayout()
        layout.addWidget(QLabel("This is a sub-window!"))
        self.setLayout(layout)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Main Window")

        button = QPushButton("Open Dialog")
        button.clicked.connect(self.show_dialog)

        central_widget = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(button)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    def show_dialog(self):
        dialog = Dialog(self)
        dialog.exec()


def main():
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())


if __name__ == "__main__":
    main()

Using a QMainWindow as the sub-window

In the examples above, the second window is a QDialog. Dialogs are the right choice for most pop-up windows — they're designed for getting input or showing information that requires a response.

However, if you need a full second window with its own menu bar, toolbar, or status bar, you can use a QMainWindow instead. The approach is the same, but you'd use show() rather than exec(), and store a reference to the window:

python
def show_sub_window(self):
    self.sub_window = SubWindow()
    self.sub_window.show()

For more on this approach, see our guide on creating multiple windows.

What's happening with clicked.connect?

The line button.clicked.connect(self.show_dialog) uses Qt's signals and slots system. When the button is clicked, it emits a clicked signal. By connecting that signal to the show_dialog method (the "slot"), we tell Qt to call that method whenever the signal fires.

This is the standard way to handle events in PyQt6. You can learn more about how this works in our signals and slots tutorial.

PyQt6 Crash Course by Martin Fitzpatrick — The important parts of PyQt6 in bite-size chunks

See the course

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

Opening a subwindow from a button event was written by Martin Fitzpatrick.

Martin Fitzpatrick is the creator of Python GUIs, and has been developing Python/Qt applications for the past 12+ years. He has written a number of popular Python books and provides Python software development & consulting for teams and startups.