Qt Designer and pyuic
Qt Designer is the visual UI editor: the drag-and-drop canvas where the layout, the widgets, and the properties are built visually, saved as the .ui file (the XML description of the UI). The pyuic (the pyuic5 for the PyQt5 line) is the compiler that turns the .ui into the Python module - the Ui_MainWindow class that builds the widget tree programmatically, which the app's own class instantiates and wires. The workflow is the design-build-compile-use: the Designer builds and saves the .ui, the pyuic compiles it to the ui_*.py, and the app's class is the QMainWindow (or the QWidget) that calls the Ui_*'s setupUi and then connects the signals. This chapter covers the Designer's construction (the layout, the widgets, the properties, the object names), the pyuic compilation, the setupUi integration pattern, and the design/code split that keeps the visual UI and the behavior separate.
The Designer: Building the UI
# the Qt Designer (the designer binary, the PyQt5-tools
# or the Qt's) - the visual editor:
# 1. the NEW Form: the QMainWindow / the QWidget / the
# QDialog (the base the form is)
# 2. the LAYOUT: the choose the layout (the vertical, the
# horizontal, the grid) - the Designer inserts the
# layout on the selected widget (the same layouts the
# code chapter covered, the visual form)
# 3. the WIDGETS: the drag from the Widget Box (the
# button, the label, the line edit, the layout items)
# into the layout - the widget lands in the layout,
# positioned by it
# 4. the PROPERTIES: the Property Editor (the right panel)
# sets the widget's properties (the text, the range,
# the enabled) - the same properties the code sets,
# the visual form
# 5. the OBJECT NAME: the property the code and the
# Designer share - the setObjectName gives the widget
# the name the compiled code references (the self.
# pushButton_1 in the generated Ui class)
# the SAVE: the .ui file (the XML) - the description of
# the widget tree + the layouts + the properties + the
# object names, the versionable asset the pyuic compiles
The Designer is the visual editor, and its construction is the five steps: the new form (the QMainWindow, the QWidget, or the QDialog base), the layout (the chosen on the selected widget - the same QVBoxLayout/QHBoxLayout/QGridLayout the code chapter covered, the visual form), the widgets (the drag from the Widget Box into the layout - the widget lands in the layout, positioned by it), the properties (the Property Editor sets the widget's properties - the text, the range, the enabled - the same properties the code sets), and the object name (the property the code and the Designer share - the setObjectName gives the widget the name the compiled code references). The save is the .ui file: the XML description of the widget tree, the layouts, the properties, and the object names - the versionable asset the pyuic compiles. The Designer's discipline is the one that makes the workflow work: the object names are the contract (the code references them), the layout is the structure (the widget's position is the layout's, not the pixel's), and the properties are the values (the design's, not the behavior's) - the .ui is the UI without the behavior, and the behavior is the next section's code.
The pyuic: Compiling the .ui
# the pyuic5 (the PyQt5 line; the pyuic6 for the PyQt6):
pyuic5 -x main.ui -o ui_main.py
# the -x: the exec() wrapper (the standalone-runnable)
# the -o: the output module (the ui_main.py)
# the result (the ui_main.py, the GENERATED):
# the class Ui_MainWindow with the setupUi method:
# def setupUi(self, MainWindow):
# MainWindow.setObjectName("MainWindow")
# self.centralwidget = QtWidgets.QWidget(MainWindow)
# self.pushButton = QtWidgets.QPushButton(self.centralwidget)
# ... # the tree, the layouts, the properties
# MainWindow.setCentralWidget(self.centralwidget)
# self.retranslateUi(MainWindow)
# QtCore.QMetaObject.connectSlotsByName(MainWindow)
# the Ui class is a MIXIN: it builds the tree on the
# passed-in widget (the MainWindow), the attributes are
# the widgets (the self.pushButton is the button), and
# the connectSlotsByName wires the auto-named slots
# (the on_pushButton_clicked, the Designer's signal
# mapping)
The pyuic5 compiles the .ui into the Python module: the pyuic5 -x main.ui -o ui_main.py reads the XML and generates the Ui_MainWindow class with its setupUi method - the setupUi builds the widget tree on the passed-in widget (the MainWindow argument), creates the child widgets as attributes (the self.pushButton is the button, the object names become the attribute names), applies the layouts and the properties, and the connectSlotsByName wires the auto-named slots (the Designer's signal-to-slot mapping, the on_pushButton_clicked for the button's clicked). The -x flag adds the exec() wrapper (the standalone-runnable form, the quick test). The generated module is the UI without the behavior: the tree, the layouts, the properties, and the auto-slot wiring - the design compiled to the code the app instantiates. The compile is the step the build runs (the .ui change re-runs the pyuic, the ui_*.py regenerated), and the ui_*.py is the generated file (the not-hand-edited, the recompile overwrites it).
The Integration: The setupUi Pattern
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from ui_main import Ui_MainWindow # the GENERATED module
# the app's class: the QMainWindow that USES the Ui class
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self) # the Ui's setupUi BUILDS the
# tree on THIS window (the
# self.pushButton now exists)
# the BEHAVIOR: the signals the Designer did not
# wire (the connectSlotsByName covers the
# auto-named; the rest is the explicit connect)
self.pushButton.clicked.connect(self.on_save)
def on_save(self):
# the handler: the widget is the self.pushButton
# (the attribute the setupUi created)
print("the save, the self.lineEdit.text():",
self.lineEdit.text())
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
The integration is the setupUi pattern: the app's class inherits the QMainWindow (the window) AND the Ui_MainWindow (the generated UI), and its __init__ calls the self.setupUi(self) - the setupUi builds the widget tree on the app's window, and the widgets become the app's attributes (the self.pushButton, the self.lineEdit - the object names as the attributes). The behavior is the app's: the signals the Designer did not auto-wire are the explicit connect in the __init__ (the self.pushButton.clicked.connect(self.on_save)), and the handlers are the app's methods (the on_save that reads the self.lineEdit.text()). The split is the point: the .ui (the design, the visual, the pyuic-compiled) is the UI structure, and the app's class is the behavior (the signals, the handlers, the logic) - the two are separate, the .ui change re-runs the pyuic without touching the app's class, and the app's class change does not touch the .ui. The setupUi is the bridge: the design compiled to the code, the code the behavior, and the split the maintainability the visual workflow gives.
The Working Rules
- The
.uiis the UI, the app's class is the behavior. The Designer builds the structure (the layout, the widgets, the properties); the app's class is the signals and the handlers. The two are separate, and thesetupUiis the bridge the app's class calls. - The object name is the contract. The
setObjectName(the Designer's property) is what the compiled code references (theself.pushButtonattribute) and what theconnectSlotsByNameauto-wires (theon_name_signalthe slot). The rename in the Designer is the rename in the code - the name is the link. - The
ui_*.pyis generated, not hand-edited. Thepyuicregenerates it from the.ui, and the edit is overwritten on the next compile - the change is the.ui(the Designer), theui_*.pyis the output. The build step re-runs thepyuicon the.uichange. - The auto-wire covers the named, the explicit covers the rest. The
connectSlotsByNamewires theon_objectName_signalslots the Designer maps; the signal the Designer did not map (or the logic that is not a one-line slot) is the explicitconnectin the app's__init__. The two coexist; the explicit is the app's. - The
.uiis the versionable asset. The XML is the design's source, the committed file the team edits in the Designer, and thepyuicthe build step that compiles it - the design in version control, the generated module the build's output.
Summary
Qt Designer is the visual UI editor: the drag-and-drop canvas where the layout, the widgets, and the properties are built, saved as the .ui file (the XML description of the widget tree, the layouts, the properties, and the object names). The pyuic5 (the pyuic6 for the PyQt6 line) compiles the .ui into the Python module - the Ui_MainWindow class with its setupUi that builds the tree on the passed-in widget, the widgets as the attributes (the object names as the attribute names), and the connectSlotsByName that wires the auto-named slots. The integration is the setupUi pattern: the app's class inherits the window and the Ui_*, calls the self.setupUi(self) in the __init__, and then connects the signals the Designer did not auto-wire (the self.pushButton.clicked.connect(self.on_save)) - the .ui is the UI structure, the app's class is the behavior, and the split is the maintainability the visual workflow gives: the .ui change re-runs the pyuic without touching the app's class, and the ui_*.py is the generated output the build regenerates. The object name is the contract the Designer and the code share, and the .ui is the versionable asset the team edits and the build compiles.