This project NEVER hardcodes UI in Python. All user interfaces are designed in Qt Designer and saved as .ui files.
# Open an existing .ui file
designer src/views/ui/main_window.ui
# Or launch Qt Designer to create a new file
designer- Choose a base widget (QMainWindow, QDialog, QWidget)
- Drag and drop widgets from the Widget Box
- Set properties in the Property Editor
- Set objectName for all widgets you'll access from Python
- Preview your design (Form → Preview)
- Save to:
src/views/ui/your_view_name.ui - Use descriptive names matching your Python class
CRITICAL: Set meaningful objectName properties in Qt Designer for widgets you'll access in Python.
Example:
- Button →
objectName:loadButton - List →
objectName:listWidget - Text Edit →
objectName:detailsText
import os
from PySide6.QtWidgets import QMainWindow, QPushButton, QListWidget
from PySide6.QtUiTools import QUiLoader
from PySide6.QtCore import QFile
class MyView(QMainWindow):
def __init__(self):
super().__init__()
self._load_ui()
def _load_ui(self):
"""Load UI from .ui file."""
ui_file_path = os.path.join(
os.path.dirname(__file__),
'ui',
'my_view.ui'
)
ui_file = QFile(ui_file_path)
if not ui_file.open(QFile.ReadOnly):
raise RuntimeError(f"Cannot open UI file: {ui_file_path}")
loader = QUiLoader()
self.ui = loader.load(ui_file, self)
ui_file.close()
# For QMainWindow
self.setCentralWidget(self.ui)
# Access widgets by objectName
self.my_button = self.ui.findChild(QPushButton, "myButton")
self.my_list = self.ui.findChild(QListWidget, "myList")After loading the UI, connect widget signals to your methods:
def _connect_signals(self):
"""Connect signals to slots."""
self.my_button.clicked.connect(self._on_button_clicked)
self.my_list.itemClicked.connect(self._on_item_clicked)
def _on_button_clicked(self):
"""Handle button click."""
print("Button clicked!")
def _on_item_clicked(self, item):
"""Handle list item click."""
print(f"Item clicked: {item.text()}")- In Qt Designer: Add menu bar items
- Set objectName for each QAction
- In Python: Find actions and connect them
# In Qt Designer: Create action with objectName="actionOpen"
# In Python:
action_open = self.ui.findChild(QAction, "actionOpen")
action_open.triggered.connect(self._on_open)- Use layout widgets (QVBoxLayout, QHBoxLayout, QGridLayout)
- Set spacing and margins in Property Editor
- Add widgets to layouts by dragging
from PySide6.QtWidgets import QDialog
class MyDialog(QDialog):
def _load_ui(self):
ui_file_path = os.path.join(
os.path.dirname(__file__),
'ui',
'my_dialog.ui'
)
ui_file = QFile(ui_file_path)
ui_file.open(QFile.ReadOnly)
loader = QUiLoader()
self.ui = loader.load(ui_file, self)
ui_file.close()
# For QDialog, set layout manually
layout = QVBoxLayout(self)
layout.addWidget(self.ui)- Use Qt Designer for ALL UI design
- Set meaningful objectName for all interactive widgets
- Use layouts for responsive design
- Preview your UI before saving
- Keep .ui files in
src/views/ui/directory - Match .ui filename with Python class name (snake_case vs PascalCase)
- Never hardcode UI in Python (
QPushButton("Text"),QVBoxLayout(), etc.) - Don't skip setting objectName - you won't be able to find widgets
- Don't use absolute positioning - use layouts instead
- Don't mix .ui files with hardcoded UI - choose one approach
Problem: findChild() returns None
Solution:
- Open .ui file in Qt Designer
- Select the widget
- In Property Editor, set
objectNameproperty - Save the .ui file
Problem: QFile.open() returns False
Solution:
- Check file path is correct
- Verify .ui file exists
- Check file permissions
- Use absolute path or
os.path.join()with__file__
Problem: Widgets overlap or don't resize properly
Solution:
- Use layout widgets (QVBoxLayout, QHBoxLayout, QGridLayout)
- Set size policies appropriately
- Set stretch factors for flexible widgets
- Preview in Qt Designer to verify
- New Form → Choose "Main Window"
- Add widgets:
- Drag QLabel to center → Set text: "Hello World"
- Set objectName:
helloLabel
- Save as:
src/views/ui/hello_window.ui
# src/views/hello_window.py
import os
from PySide6.QtWidgets import QMainWindow, QLabel
from PySide6.QtUiTools import QUiLoader
from PySide6.QtCore import QFile
class HelloWindow(QMainWindow):
"""Simple hello window."""
def __init__(self):
super().__init__()
self._load_ui()
self._setup_content()
def _load_ui(self):
ui_file_path = os.path.join(
os.path.dirname(__file__),
'ui',
'hello_window.ui'
)
ui_file = QFile(ui_file_path)
ui_file.open(QFile.ReadOnly)
loader = QUiLoader()
self.ui = loader.load(ui_file, self)
ui_file.close()
self.setCentralWidget(self.ui)
# Access the label
self.hello_label = self.ui.findChild(QLabel, "helloLabel")
def _setup_content(self):
"""Set dynamic content."""
self.hello_label.setText("Hello from Python!")
# Usage in main.py
if __name__ == "__main__":
from PySide6.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
window = HelloWindow()
window.show()
sys.exit(app.exec())Remember: Design in Qt Designer → Load in Python → Connect signals
This workflow keeps UI design visual and maintainable while keeping Python code focused on behavior and business logic.