Skip to main content

Hot Reload

Hot reload rebuilds your UI in place when its generated source changes — no process restart, no lost window state. The library provides it at two levels:

  • Component hot reload — leaf forms embedded in a QCustomComponentContainer rebuild themselves when their compiled .py changes.
  • Main-window hot reload — a top-level window rebuilds its whole UI (re-running setupUi and re-connecting signals) via enable_hot_reload.

Both pair with the dev workflow that regenerates src/ui_*.py from your .ui files — the dev server, --monitor-ui, or the Run button in Designer. Edit a form, save, and the running app updates.


Why the split?

A component is a leaf form with no external references — the container owns it, so it can be torn down and rebuilt freely.

A main window is different: its widgets are wired to your application code (signal/slot connections, references you hold). Rebuilding them blindly would orphan those connections. So the two paths differ: the container rebuilds components automatically, while a main window supplies a build() callable that re-creates the UI and re-wires it.


Component hot reload

QCustomComponentContainer loads a compiled UI module (the ui_<name>.py produced by Custom_Widgets --convert-ui). Its hotReload property (default True) installs a file watcher on that module and rebuilds the embedded form in place whenever it changes.

from Custom_Widgets.QCustomComponentContainer import QCustomComponentContainer

container = QCustomComponentContainer()
container.filePath = "src/ui_dashboard.py" # compiled module
container.formClassName = "Ui_Dashboard"
container.hotReload = True # default; rebuilds on change

Now, while the app runs, regenerating src/ui_dashboard.py (by saving ui/dashboard.ui under the dev server or --monitor-ui) rebuilds the embedded form instantly.

.ui paths resolve automatically

The loader only accepts compiled modules at runtime, but you can still point filePath at a raw .ui — it is transparently resolved to the sibling ui_<stem>.py (looked up next to the .ui first, then in the default src/ output directory):

container.filePath = "ui/dashboard.ui"   # -> resolves to ui_dashboard.py

Only a form with no compiled module at all is rejected (with a clear message telling you to run --convert-ui). Existing projects that still point at .ui files keep working with no edits.


Main-window hot reload

enable_hot_reload rebuilds a top-level window's UI in place when its compiled Ui_ module(s) change. Because the window is wired to your app code, you pass a build() callable that re-runs setupUi and re-connects signals; the helper calls it now and again on every change. Window geometry is preserved.

from Custom_Widgets import enable_hot_reload

class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# Build now, and rebuild in place whenever the compiled UI changes.
enable_hot_reload(self, self.build)

def build(self):
# IMPORTANT: import the Ui_ class INSIDE build() so the reloaded
# module is picked up on each rebuild.
from src.ui_mainwindow import Ui_MainWindow
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# re-connect signals every rebuild
self.ui.saveButton.clicked.connect(self.on_save)

Signature

enable_hot_reload(window, build, watch=None, src_dir=None)
ArgumentDescription
windowThe top-level widget being (re)built by build.
buildZero-arg callable that constructs the UI (setupUi + signal connections). Called once immediately, then after each change. Import the Ui_ class inside build.
watchOptional explicit list of .py files to watch. Default: the ui_*.py modules build imported (discovered after the first build), else every ui_*.py under src_dir.
src_dirGenerated-source directory (default <projectRoot>/src).

Returns the QFileSystemWatcher (also stored on the window).

tip

New projects created with ProjectMaker already use this pattern — the generated main.py wraps setupUi in build() and calls enable_hot_reload, so main-window hot reload works out of the box.


How it fits the dev loop

When you run your app under the dev server (the CLI, or Run in Designer), changes are classified so the right thing happens:

  • .scss / .json — the app live-reloads styles.
  • .ui — regenerated to src/ui_*.py; embedded component forms hot-reload in place. A form imported by the entry script (a main-window form) triggers a restart unless the app opted into enable_hot_reload, in which case it rebuilds in place too.
  • Hand-written .py outside the generated dir — a full restart.

The result: design in Qt Designer, press Ctrl+S, and see the change in the live app — components and (with enable_hot_reload) the main window included.


See also