PyQt6 Patterns Skill
Guide for building Desktop applications with PyQt6, focusing on architecture, threading, and user experience.
🏗️ Architecture Pattern
Use a model that separates UI and Logic:
- •MainWindow: Manages UI, Layout, Signals.
- •Worker Thread (QThread): Handles long-running tasks (IO, Network, Heavy computation).
- •Core Logic: Pure Python functions, independent of GUI.
Example Structure main.py
# Imports
from PyQt6.QtWidgets import ...
from PyQt6.QtCore import QThread, pyqtSignal
# 1. Background Thread Class
class WorkerThread(QThread):
progress = pyqtSignal(int, str)
finished = pyqtSignal(object)
error = pyqtSignal(str)
def run(self):
try:
# Heavy task here
pass
except Exception as e:
self.error.emit(str(e))
# 2. Main Window Class
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setup_ui()
def start_task(self):
self.thread = WorkerThread(...)
self.thread.progress.connect(self.on_progress)
self.thread.finished.connect(self.on_finished)
self.thread.start()
🧵 Threading (Critical)
Inviolable Rule: NEVER run heavy tasks on the Main Thread.
Why?
- •Blocks Main Thread -> UI freezes (Not Responding).
- •On macOS: Causes "Beachball of death".
Standard Pattern
Use QThread:
- •Create a class inheriting from
QThread. - •Define Signals (
pyqtSignal) to communicate back to Main Thread. - •Override
run()method. - •Initialize and keep reference to thread (
self.thread) in MainWindow. - •Connect signals and call
start().
🎨 UI & Layouts
Layout Hierarchy
Always use Layouts for responsive UI:
QMainWindow
└── CentralWidget (QWidget)
└── QVBoxLayout
├── QGroupBox ("Input")
│ └── QFormLayout
├── QGroupBox ("Settings")
│ └── QVBoxLayout
└── QGroupBox ("Actions")
└── QHBoxLayout
Styles
Use Fusion style for a clean cross-platform look:
app = QApplication(sys.argv)
app.setStyle("Fusion")
⚠️ Error Handling
Pattern: Try-Except-Signal
In Worker Thread, always use try-except and emit error signal:
def run(self):
try:
# Dangerous code
do_work()
except Exception as e:
self.error.emit(str(e)) # Send error to UI
```
In UI, listen for signal and show MessageBox:
```python
def on_error(self, message):
self.btn_start.setEnabled(True) # Re-enable UI
QMessageBox.critical(self, "Error", message)
📝 Widget Common Patterns
File Browsing
path = QFileDialog.getExistingDirectory(self, "Select Folder")
if path:
self.input_dir = path
self.label.setText(path)
Progress Bar
- •Unknown duration:
progressBar.setRange(0, 0) - •Known duration:
emit(percent)from thread ->progressBar.setValue(percent)
Logs Display
Use QTextEdit readonly to display realtime logs:
self.log_text = QTextEdit() self.log_text.setReadOnly(True) # Append log signal self.log_text.append(message)
✅ Best Practices Checklist
- • Always using QThread for tasks taking > 0.1s
- • Handling Exceptions in thread and reporting to UI
- • Disabling Start button while running
- • Clear code structure (Imports -> Thread -> Window -> Main)
- • Using Type Hinting for readable code
🤖 Agentic Protocol
Skill Metadata
- •Version: 1.0.0
- •Last Updated: 2026-01-27
1. Activation Log
When activating this skill (generating code), print: "🎯 [SKILL ACTIVATED] pyqt6-patterns v1.0.0" "📋 Parameters:" " - Component: [MainWindow|WorkerThread|Dialog]" " - Pattern Applied: [Threading|Layout|Signal-Slot]"
2. User Confirmation
Before applying major architectural changes: "I'm implementing the [Pattern Name] pattern for [Component]. This will structure the code as [Description]. Proceed?"
3. Completion Log
- •Success: "✅ [pyqt6-patterns] Implementation ready. Validated imports and signals."
- •Warning: "⚠️ [pyqt6-patterns] Note: Ensure [dependency] is installed."