design.md
16.5 KB
Design: PySide6 Migration Architecture
Overview
This document describes the architectural design for migrating from tkinter to PySide6, including class structure, layout design, and implementation patterns.
Architecture
Application Structure
image_generator.py (Qt-based)
├── LoginDialog (QDialog)
│ ├── Database authentication
│ ├── Remember me functionality
│ └── Returns: (success, username, remember_user, remember_password, password_hash)
│
├── ImageGeneratorWindow (QMainWindow)
│ ├── Central widget with main layout
│ ├── Reference images section
│ ├── Prompt section
│ ├── Settings section
│ ├── Action buttons
│ └── Preview area
│
└── Worker Threads (QThread)
└── ImageGenerationWorker
└── Handles async image generation
LoginDialog Design
Class Structure
class LoginDialog(QDialog):
"""
Qt-based login dialog
Replaces: LoginWindow (tkinter)
"""
def __init__(self, db_config, last_user="", saved_password_hash=""):
super().__init__()
self.db_config = db_config
self.last_user = last_user
self.saved_password_hash = saved_password_hash
self.success = False
self.authenticated_user = ""
self.password_changed = False
self.current_password_hash = ""
self.setup_ui()
def setup_ui(self):
"""Build UI using Qt layouts"""
# Will use QVBoxLayout for vertical stacking
# QFormLayout for label+input pairs
# QHBoxLayout for checkbox row
Layout Design
┌─────────────────────────────────────┐
│ LoginDialog │
│ ┌───────────────────────────────┐ │
│ │ QVBoxLayout (main) │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ QLabel: "登录" │ │ │
│ │ │ (title, centered) │ │ │
│ │ └─────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ QFormLayout: │ │ │
│ │ │ "用户名:" - QLineEdit │ │ │
│ │ │ "密码:" - QLineEdit │ │ │
│ │ └─────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ QHBoxLayout: │ │ │
│ │ │ ☑ 记住用户名 │ │ │
│ │ │ ☑ 记住密码 │ │ │
│ │ └─────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ QPushButton: "登录" │ │ │
│ │ └─────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ QLabel: error_label │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
Styling Approach
Use QSS (Qt Style Sheets) for consistent styling:
def apply_styles(self):
self.setStyleSheet("""
QDialog {
background-color: #ffffff;
}
QLabel#title {
font-size: 20pt;
font-weight: bold;
color: #1d1d1f;
}
QLabel#field_label {
font-size: 10pt;
color: #666666;
}
QLineEdit {
padding: 8px;
border: 1px solid #e5e5e5;
border-radius: 4px;
background-color: #fafafa;
font-size: 11pt;
}
QPushButton#login_button {
background-color: #007AFF;
color: white;
font-size: 10pt;
font-weight: bold;
padding: 10px 20px;
border: none;
border-radius: 6px;
}
QPushButton#login_button:hover {
background-color: #0051D5;
}
QLabel#error_label {
color: #ff3b30;
font-size: 9pt;
}
""")
ImageGeneratorWindow Design
Class Structure
class ImageGeneratorWindow(QMainWindow):
"""
Qt-based main application window
Replaces: ImageGeneratorApp (tkinter)
"""
def __init__(self):
super().__init__()
self.api_key = ""
self.uploaded_images = [] # List of file paths
self.generated_image_data = None
self.generated_image_bytes = None
self.saved_prompts = []
self.load_config()
self.setup_ui()
def setup_ui(self):
"""Build main window UI"""
# Central widget with QVBoxLayout
# Sections organized with QGroupBox or QFrame
Layout Design
┌──────────────────────────────────────────────────────────┐
│ ImageGeneratorWindow (QMainWindow) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ QVBoxLayout (central widget) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ QGroupBox: "参考图片" │ │ │
│ │ │ ┌────────────────────────────────────────┐ │ │ │
│ │ │ │ QHBoxLayout: │ │ │ │
│ │ │ │ QPushButton: "+ 添加图片" │ │ │ │
│ │ │ │ QLabel: "已选择 X 张" │ │ │ │
│ │ │ └────────────────────────────────────────┘ │ │ │
│ │ │ ┌────────────────────────────────────────┐ │ │ │
│ │ │ │ QScrollArea (horizontal): │ │ │ │
│ │ │ │ Image previews with delete buttons │ │ │ │
│ │ │ └────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ QHBoxLayout (content row) │ │ │
│ │ │ ┌────────────────┐ ┌──────────────────┐ │ │ │
│ │ │ │ QGroupBox: │ │ QGroupBox: │ │ │ │
│ │ │ │ "提示词" │ │ "生成设置" │ │ │ │
│ │ │ │ - Toolbar │ │ - 宽高比 │ │ │ │
│ │ │ │ - QTextEdit │ │ - 图片尺寸 │ │ │ │
│ │ │ └────────────────┘ └──────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ QHBoxLayout (action buttons) │ │ │
│ │ │ QPushButton: "生成图片" │ │ │
│ │ │ QPushButton: "下载图片" │ │ │
│ │ │ QLabel: status │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ QGroupBox: "预览" │ │ │
│ │ │ ┌────────────────────────────────────────┐ │ │ │
│ │ │ │ QLabel (image display) │ │ │ │
│ │ │ │ - Scaled pixmap │ │ │ │
│ │ │ │ - Double-click to open │ │ │ │
│ │ │ └────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
Threading Design
Qt uses QThread for background operations:
class ImageGenerationWorker(QThread):
"""
Worker thread for image generation
Replaces: threading.Thread
"""
# Signals for communication
finished = Signal(bytes) # Image bytes
error = Signal(str) # Error message
progress = Signal(str) # Status updates
def __init__(self, api_key, prompt, images, config):
super().__init__()
self.api_key = api_key
self.prompt = prompt
self.images = images
self.config = config
def run(self):
"""Execute in background thread"""
try:
# Generate image
image_bytes = self.generate_image()
self.finished.emit(image_bytes)
except Exception as e:
self.error.emit(str(e))
# Usage in main window:
def generate_image_async(self):
self.worker = ImageGenerationWorker(...)
self.worker.finished.connect(self.on_image_generated)
self.worker.error.connect(self.on_generation_error)
self.worker.progress.connect(self.update_status)
self.worker.start()
Benefits over threading.Thread:
- Thread-safe signal/slot mechanism
- No need for
root.after()for UI updates - Automatic cleanup
- Better error handling
Data Flow
Configuration Management
# Same as tkinter version
def load_config(self):
"""Load from config.json"""
# Identical logic, just different widget updates
def save_config(self):
"""Save to config.json"""
# Identical logic
Authentication Flow
# In main.py or __main__:
app = QApplication(sys.argv)
# Show login dialog
login_dialog = LoginDialog(db_config, last_user, saved_password_hash)
if login_dialog.exec() == QDialog.Accepted:
# Login successful
main_window = ImageGeneratorWindow()
main_window.show()
sys.exit(app.exec())
else:
# Login cancelled or failed
sys.exit(0)
Styling Strategy
Approach 1: QSS (Recommended)
# Define styles in QSS (similar to CSS)
app.setStyleSheet("""
QGroupBox {
font-weight: bold;
border: 1px solid #e5e5e5;
border-radius: 6px;
margin-top: 6px;
padding-top: 10px;
}
QGroupBox::title {
color: #1d1d1f;
}
""")
Benefits:
- Centralized styling
- Easy to modify
- Similar to web CSS
- Can load from external file
Approach 2: Programmatic Styling
# Set properties directly
button = QPushButton("生成图片")
button.setStyleSheet("background-color: #007AFF; color: white;")
font = QFont("Segoe UI", 10, QFont.Bold)
button.setFont(font)
Recommendation: Use QSS for consistent styling, programmatic for dynamic changes.
Image Handling
Display Images
# Tkinter used PIL ImageTk.PhotoImage
# Qt uses QPixmap
# Load and display
pixmap = QPixmap(file_path)
scaled_pixmap = pixmap.scaled(
width, height,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
image_label.setPixmap(scaled_pixmap)
Thumbnails
# Create thumbnail
thumbnail = pixmap.scaled(
100, 100,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
Migration Compatibility
Preserve Functionality
All existing features must work identically:
| Feature | tkinter Implementation | Qt Implementation |
|---|---|---|
| Login |
tk.Toplevel + mainloop()
|
QDialog.exec() |
| Remember me | tk.BooleanVar |
QCheckBox.isChecked() |
| Password hash | String storage | Identical |
| Config save | JSON file | Identical |
| Image upload | filedialog.askopenfilenames |
QFileDialog.getOpenFileNames |
| Image generation | threading.Thread |
QThread |
| Status updates | root.after() |
Signal.emit() |
| Image display | PhotoImage |
QPixmap |
Error Handling
# Qt has better exception handling in threads
class ImageGenerationWorker(QThread):
error = Signal(str)
def run(self):
try:
# ... generation code ...
except Exception as e:
self.error.emit(str(e))
# In main window:
def on_generation_error(self, error_msg):
QMessageBox.critical(self, "错误", f"生成失败: {error_msg}")
Testing Strategy
Unit Testing
# Qt has QTest framework
from PySide6.QtTest import QTest
def test_login_dialog():
dialog = LoginDialog(db_config)
# Simulate user input
dialog.username_entry.setText("test_user")
dialog.password_entry.setText("password")
# Simulate button click
QTest.mouseClick(dialog.login_button, Qt.LeftButton)
assert dialog.success == True
Manual Testing Checklist
- macOS: All UI elements visible
- Windows: Native look and feel
- Login: Authentication works
- Image generation: API calls work
- File operations: Upload/download work
- Config: Save/load works
- Threading: No UI freezing
Performance Considerations
Qt Advantages:
- Native rendering (faster than tkinter)
- Better memory management
- Hardware acceleration support
- Efficient image handling
Optimization:
- Use QPixmap cache for repeated images
- Lazy-load image thumbnails
- Use QThread for all I/O operations
- Implement loading indicators
Future Enhancements
With Qt, we can easily add:
- Drag-and-drop image upload
- Progress bars for generation
- Animations and transitions
- Custom window chrome
- System tray integration
- Better keyboard shortcuts
- Undo/redo functionality
- More sophisticated layouts
Conclusion
The Qt architecture provides:
- Reliability: Native rendering solves macOS issues
- Maintainability: Cleaner code structure
- Extensibility: Easy to add features
- Performance: Better than tkinter
- Cross-platform: Consistent behavior
This design preserves all existing functionality while providing a solid foundation for future improvements.