Reloading UI Changes in PyQt6 Without Restarting PyCharm

How to see your Qt Designer changes immediately during development
Heads up! You've already completed this tutorial.

I'm developing a PyQt app using Qt Designer and PyCharm. Every time I modify my .ui file and connect it to a new button or widget, I have to restart my IDE before the changes show up. Is there a way to reload UI changes without restarting PyCharm?

This is a common frustration when developing PyQt6 applications with Qt Designer. You make a small change to your .ui file, but your running application (or even your next run) doesn't pick up the changes. The root cause usually comes down to how Python caches imported modules, and how your .ui files are being loaded. Let's walk through the problem and look at a few practical solutions.

Why This Happens

When you convert a .ui file to a Python file using pyuic6 and then import it, Python caches that module. Even if you regenerate the .py file from your updated .ui file, Python's import system may still use the cached (old) version. This is standard Python behavior — modules are only imported once per interpreter session, and subsequent import statements return the cached version.

Inside an IDE like PyCharm, the Python interpreter may persist between runs depending on your run configuration, which can make this caching even stickier.

Solution 1: Run Your App From an External Terminal

The simplest and most reliable approach is to run your application from a terminal (command prompt or shell) outside of your IDE. Each time you run your script from the terminal, a fresh Python interpreter starts, so there's no module caching between runs.

In practice, this is fast:

  1. Open a terminal window alongside PyCharm.
  2. Navigate to your project directory.
  3. Run your script with python your_script.py.
  4. When you make changes, press the up arrow key to recall the last command and hit Enter.

This gives you a clean slate every time and avoids any IDE-specific quirks.

Solution 2: Load .ui Files Dynamically at Runtime

Instead of converting your .ui file to Python code and importing it, you can load the .ui file directly at runtime using uic.loadUi(). This way, every time you call loadUi(), it reads the current version of the file from disk — no caching involved.

Here's a minimal example:

python
import sys

from PyQt6.QtWidgets import QApplication, QMainWindow
from PyQt6 import uic


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi("window.ui", self)

        # Connect signals to your widgets here.
        # Widget names come from Qt Designer's object names.
        self.bt_one.clicked.connect(self.on_button_one)

    def on_button_one(self):
        self.lineedit.setText("Button one clicked!")


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

With this approach, you edit your .ui file in Qt Designer, save it, and then simply re-run your script. The latest version of the .ui file is always loaded fresh. There's no intermediate .py file to worry about.

Solution 3: Hot-Reload the UI While the App Is Running

If you want to go a step further, you can add a "Reload UI" action to your application that reloads the .ui file without closing the app. This is handy during development when you're iterating on your layout and want to see changes immediately.

Here's a complete working example that demonstrates this:

python
import sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QApplication, QMainWindow
from PyQt6 import uic


class MainWindow(QMainWindow):
    def __init__(self, ui_file):
        super().__init__()
        self.ui_file = ui_file
        self.load_ui()
        self.setup_dev_menu()

    def load_ui(self):
        """Load (or reload) the .ui file from disk."""
        uic.loadUi(self.ui_file, self)
        self.connect_signals()
        print(f"UI loaded from {self.ui_file}")

    def connect_signals(self):
        """Connect widget signals after loading the UI.

        You need to reconnect signals each time the UI is
        reloaded, since the widgets are recreated.
        """
        # These widget names must match the objectName
        # values you set in Qt Designer.
        try:
            self.bt_one.clicked.connect(
                lambda: self.lineedit.setText("Button one")
            )
            self.bt_two.clicked.connect(
                lambda: self.lineedit.setText("Button two")
            )
            self.bt_three.clicked.connect(
                lambda: self.lineedit.setText("Button three")
            )
        except AttributeError as e:
            print(f"Could not connect signal (widget missing?): {e}")

    def setup_dev_menu(self):
        """Add a developer menu with a reload action."""
        menu = self.menuBar()
        dev_menu = menu.addMenu("Dev")

        reload_action = QAction("Reload UI", self)
        reload_action.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_R)
        reload_action.triggered.connect(self.load_ui)
        dev_menu.addAction(reload_action)

        quit_action = QAction("Quit", self)
        quit_action.setShortcut(Qt.Key.Key_Escape)
        quit_action.triggered.connect(self.close)
        dev_menu.addAction(quit_action)


app = QApplication(sys.argv)
window = MainWindow("window.ui")
window.show()
app.exec()

With this setup, your development workflow becomes:

  1. Run the application once.
  2. Edit your .ui file in Qt Designer and save.
  3. Press Ctrl+R in your running app (or use the Dev menu) to reload the UI.

The load_ui method reads the .ui file fresh from disk each time, and connect_signals re-wires your button connections to the newly created widgets.

The try/except block in connect_signals is there as a safety net — if you remove a widget from your .ui file but haven't updated the code yet, you'll get a helpful message in the console instead of a crash.

A Note About pyuic6 vs. loadUi

There are two common ways to use .ui files in PyQt6:

Approach How it works Caching behavior
pyuic6 (compile to .py) Converts .ui to a Python module you import Python caches the import — changes require a fresh interpreter
uic.loadUi() (load at runtime) Reads the .ui file directly from disk Always reads the latest version

For production applications, some developers prefer the pyuic6 approach because it removes the runtime dependency on the .ui file. But during development, loadUi() is much more convenient because it always reflects your latest changes.

You can always switch to the compiled approach later when you're ready to package your application.

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

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick

(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!

More info Get the book

Martin Fitzpatrick

Reloading UI Changes in PyQt6 Without Restarting PyCharm 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.