I'm developing a PyQt app using Qt Designer and PyCharm. Every time I modify my
.uifile 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:
- Open a terminal window alongside PyCharm.
- Navigate to your project directory.
- Run your script with
python your_script.py. - 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:
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:
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:
- Run the application once.
- Edit your
.uifile in Qt Designer and save. - 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.
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!