关于 pyside6-qml-architecture
为 Python + PySide6 与 QML 桌面应用规定 MVC 架构、项目结构和各层职责,适合构建分层清晰的 GUI 项目。内容涵盖启动与依赖注入模式,以及层间信号流和设计约束。
主要功能
- 01规定 MVC 分层职责
- 02定义 Python-QML 桥接层职责
- 03提供应用启动与依赖注入模式
- 04说明信号与属性绑定通信
- 05给出项目结构及文件拆分建议
适用场景
- 01规划 PySide6 QML 应用目录结构
- 02梳理视图、控制器与服务边界
- 03设计应用启动及层间通信流程
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ds-codi/ project-memory-mcp --skill pyside6-qml-architecture安装后在 Claude Code 中输入/pyside6-qml-architecture确认已加载。
详细介绍
PySide6 QML MVC 架构
此工作区中的桌面 GUI 应用使用 Python + PySide6,并以 QML 文件构建视图层,遵循严格的模型-视图-控制器(MVC)架构。本技能介绍了从 ds_pas/ 应用中提炼出的规范项目结构、引导模式和各层职责。
架构概览
┌─────────────────────────────────────────┐
│ View Layer (QML files) │
│ Declarative UI, data binding, signals │
└──────────────────┬──────────────────────┘
│ Properties, Signals, Slots
┌──────────────────▼──────────────────────┐
│ Python-QML Bridge │
│ QObject subclasses exposed to QML │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Controller Layer │
│ Coordinate models & services │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Model Layer │
│ Data structures, validation, signals │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Services Layer │
│ Database, files, network, broker │
└─────────────────────────────────────────┘
项目结构
my_app/
├── app.py # Bootstrap & DI container (singleton)
├── __init__.py # Package init
├── __main__.py # Entry point: python -m my_app
├── controllers/
│ ├── __init__.py
│ ├── base.py # BaseController with view registry
│ └── *_controller.py # Domain controllers (job, settings, etc.)
├── models/
│ ├── __init__.py
│ ├── base.py # BaseModel with property_changed signal
│ ├── state.py # ApplicationState singleton
│ └── *.py # Domain models (job, piece, customer, etc.)
├── views/
│ ├── __init__.py
│ ├── bridge.py # QObject bridge classes exposed to QML
│ ├── main_window.py # Main window setup (QQmlApplicationEngine)
│ └── components/ # Reusable Python view helpers
├── services/
│ ├── __init__.py
│ ├── database/ # Repository pattern for data access
│ └── *.py # External interaction services
├── resources/
│ ├── qml/
│ │ ├── main.qml # Root QML component
│ │ ├── components/ # Reusable QML components
│ │ ├── pages/ # Page-level QML views
│ │ └── styles/ # Theme and style definitions
│ ├── icons/ # SVG/PNG icons
│ └── qml.qrc # Qt resource file (optional)
├── utils/
│ ├── __init__.py
│ ├── signals.py # Central SignalRegistry
│ ├── types.py # Type aliases, protocols, ServiceLocator
│ └── paths.py # Path resolution helpers
└── tests/
└── *.py
各层职责
| 组件 | 职责 | 不得 |
|---|---|---|
| 模型 | 数据结构、验证、序列化、property_changed 信号 | 操作 UI、调用服务、引用 QML |
| 视图(QML) | 声明式 UI 布局、将数据绑定到桥接对象属性、捕获用户输入 | 包含业务逻辑、直接调用服务 |
| 桥接对象 | 向 QML 暴露模型数据和控制器操作的 QObject 子类 | 包含业务逻辑、直接操作 QML |
| 控制器 | 协调模型和服务、处理操作、发出信号 | 直接操作 UI、导入 QML 类型 |
| 服务 | 数据库查询、文件 I/O、网络调用、IPC | 引用模型、视图或控制器 |
应用引导(DI 容器)
应用类是一个负责将所有层连接在一起的单例:
"""app.py — Bootstrap & DI container."""
import sys
import logging
from typing import Any
from pathlib import Path
from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QObject, QUrl
from my_app.utils.signals import SignalRegistry, get_signal_registry
from my_app.utils.types import ServiceLocator
logger = logging.getLogger(__name__)
class MyApplication:
"""Singleton application with DI container."""
_instance: "MyApplication | None" = None
def __new__(cls, dev_mode: bool = False) -> "MyApplication":
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, dev_mode: bool = False) -> None:
if self._initialized:
return
self._initialized = True
self._dev_mode = dev_mode
self._qt_app: QApplication | None = None
self._engine: QQmlApplicationEngine | None = None
self._services: dict[str, Any] = {}
self._controllers: dict[str, Any] = {}
self._signals = get_signal_registry()
self._service_locator = ServiceLocator()
def _register_services(self) -> None:
"""Create and register all service instances."""
# self._services["db"] = DatabaseService(...)
pass
def _register_controllers(self) -> None:
"""Create controllers with injected dependencies."""
# self._controllers["job"] = JobController(
# signals=self._signals,
# repository=self._services["db"],
# )
pass
def _register_qml_types(self) -> None:
"""Register Python bridge objects as QML context properties."""
ctx = self._engine.rootContext()
# ctx.setContextProperty("jobBridge", self._bridges["job"])
pass
def run(self) -> int:
self._qt_app = QApplication(sys.argv)
self._register_services()
self._register_controllers()
self._engine = QQmlApplicationEngine()
self._register_qml_types()
qml_path = Path(__file__).parent / "resources" / "qml" / "main.qml"
self._engine.load(QUrl.fromLocalFile(str(qml_path)))
if not self._engine.rootObjects():
return -1
return self._qt_app.exec()
入口点
"""__main__.py"""
import sys
from my_app.app import MyApplication
def main():
app = MyApplication(dev_mode="--dev" in sys.argv)
sys.exit(app.run())
if __name__ == "__main__":
main()
ServiceLocator 模式
"""utils/types.py — Type aliases, protocols, and DI container."""
from typing import Any, Protocol, TypeVar, runtime_checkable
T = TypeVar("T")
@runtime_checkable
class IController(Protocol):
def initialize(self) -> None: ...
def cleanup(self) -> None: ...
@runtime_checkable
class IService(Protocol):
def initialize(self) -> None: ...
def shutdown(self) -> None: ...
class ServiceLocator:
"""Lightweight DI container for service instances."""
_instance: "ServiceLocator | None" = None
def __new__(cls) -> "ServiceLocator":
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._services = {}
return cls._instance
def register(self, name: str, service: Any) -> None:
self._services[name] = service
def get(self, name: str) -> Any:
if name not in self._services:
raise KeyError(f"Service '{name}' not registered")
return self._services[name]
def has(self, name: str) -> bool:
return name in self._services
信号流
User Action (QML)
↓
QML emits signal / calls slot on Bridge
↓
Bridge delegates to Controller
↓
Controller calls Service
↓
Service returns result
↓
Controller updates Model
↓
Model emits property_changed
↓
Bridge property notifies QML (via NOTIFY)
↓
QML binding automatically updates UI
关键设计规则
- QML 文件仅用于声明 —— 不包含 JavaScript 业务逻辑,不直接调用服务
- Python 桥接对象是 QML 与 Python 后端之间唯一的接口
- 控制器绝不导入 QML 类型 —— 通过桥接对象的信号和属性进行操作
- 模型是纯数据 —— 不导入 UI,不调用服务
- 服务是无状态的工作单元 —— 不引用模型,不了解 UI
- 所有跨层通信均通过
SignalRegistry或 Qt 属性绑定进行 - 单例(
Application、SignalRegistry、ServiceLocator、ApplicationState)使用__new__模式以实现线程安全的复用
文件大小指南
- 让文件专注于单一职责
- 将超过约 300-400 行的文件拆分为子模块
- 将可复用的 QML 组件提取到
resources/qml/components/ - 按领域对相关控制器和服务进行分组(例如,
controllers/job_controller.py)
参考资料
评论
支持 Markdown