mcpField
P
pyqt6-ui-development-rules

pyqt6-ui-development-rules

github.com/oimiragieo开发441.1k 次安装17 周前更新
代码仓库

关于 pyqt6-ui-development-rules

为 PyQt6 桌面 GUI 开发提供规范的技能,面向构建、重构和调试 Qt 应用的开发者。重点涵盖 MVC、线程、主题与跨平台布局。

主要功能

  1. 01MVC 分层与信号槽通信
  2. 02QSS 应用级主题配置
  3. 03QThread 与线程池后台任务
  4. 04布局管理器构建响应式界面
  5. 05跨平台 DPI 与无障碍支持

适用场景

  1. 01搭建新的 PyQt6 桌面应用
  2. 02重构现有 PyQt 或 PySide 代码
  3. 03排查界面冻结与无响应
  4. 04实现自定义控件和复杂布局

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/oimiragieo/agent-studio --skill pyqt6-ui-development-rules

安装后在 Claude Code 中输入/pyqt6-ui-development-rules确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

commands/
pyqt6-ui-development-rules.md
hooks/
post-execute.cjs
pre-execute.cjs
references/
research-requirements.md
rules/
pyqt6-ui-development-rules.md
schemas/
input.schema.json
output.schema.json
scripts/
main.cjs
templates/
implementation-template.md

详细介绍

PyQt6 UI 开发规则技能

PyQt6 桌面 GUI 开发专家,负责落实 MVC 分离、信号/槽架构、QSS 主题设置、线程并发和跨平台渲染最佳实践。确保桌面应用响应灵敏、易于访问且视觉风格一致。 - 设计采用 MVC 分离的 PyQt6 应用架构 - 实现 UI 与业务逻辑之间的信号/槽通信模式 - 配置应用级 QSS 主题,支持深色/浅色模式 - 使用 QThread、QRunnable 和 QThreadPool 管理后台操作 - 使用 QVBoxLayout、QHBoxLayout、QGridLayout 和 QFormLayout 构建自适应布局 - 实现自定义 QWidget 子类,并正确处理 paintEvent - 设置支持跨平台的 DPI 感知渲染 - 配置无障碍功能(屏幕阅读器支持、键盘导航)

概述

此技能为构建可用于生产环境的 PyQt6 桌面应用制定规则。核心原则是:通过信号/槽严格分离 MVC、绝不阻塞 UI 线程、通过 QSS 集中管理主题,以及使用布局管理器驱动自适应设计。这些规则可以避免最常见的 PyQt6 问题:UI 冻结、难以测试的耦合,以及特定平台的渲染错误。

适用场景

  • 构建新的 PyQt6 桌面应用
  • 将现有 PyQt/PySide 代码重构为 PyQt6
  • 调试冻结或无响应的 Qt UI
  • 实现自定义控件或复杂布局
  • 设置跨平台桌面应用构建

铁律

  1. 始终使用 Qt 的信号/槽机制进行 UI 与逻辑之间的通信——UI 层与业务逻辑层之间直接调用方法会破坏 MVC 分离,并造成难以测试的耦合。
  2. 绝不在主 UI 线程上执行耗时操作——阻塞 Qt 事件循环会导致界面无响应,并触发操作系统的“未响应”对话框。
  3. 始终在 QApplication 级别应用 QSS 样式表,而不是逐个控件应用——控件级内联样式会导致主题不一致,并使样式管理变得杂乱且难以维护。
  4. 绝不使用绝对像素坐标布置控件——应使用 Qt 布局管理器(QVBoxLayout、QHBoxLayout、QGridLayout),以确保 DPI 感知和跨平台渲染。
  5. 始终在发布前于所有目标平台上测试 UI——PyQt6 的渲染、字体缩放和控件尺寸在 Windows、macOS 和 Linux 之间存在差异。

反模式

反模式失败原因正确做法
从 UI 槽函数直接调用业务逻辑UI 与逻辑耦合;无法测试,并破坏 MVC 架构从 UI 发出信号;通过槽函数连接到控制器/服务方法
在主线程中执行网络或文件 I/O阻塞 Qt 事件循环;操作完成前 UI 会冻结使用 QThread、QRunnable,或使用 qasync 的 asyncio 执行后台操作
硬编码像素尺寸和位置在高 DPI 显示器和不同 OS DPI 缩放设置下会失效使用布局管理器和尺寸策略;使用 logicalDpiX() 实现 DPI 感知的尺寸设置
为单个控件设置内联样式导致视觉不一致;主题化或维护极其困难在 QApplication 级别定义统一的 QSS 样式表,并使用对象名称/类
忽略跨平台渲染差异Windows/macOS/Linux 上的控件尺寸、字体和边距差异很大在所有目标平台上测试;在渲染存在差异的地方使用平台条件逻辑

工作流

步骤 1:应用架构(MVC)
# model.py -- Business logic, no Qt dependencies
class DataModel:
    def __init__(self):
        self._items = []

    def add_item(self, item: str) -> bool:
        if item and item not in self._items:
            self._items.append(item)
            return True
        return False

# controller.py -- Mediates between Model and View
from PyQt6.QtCore import QObject, pyqtSignal

class Controller(QObject):
    items_changed = pyqtSignal(list)
    error_occurred = pyqtSignal(str)

    def __init__(self, model: DataModel):
        super().__init__()
        self._model = model

    def add_item(self, item: str) -> None:
        if self._model.add_item(item):
            self.items_changed.emit(self._model._items.copy())
        else:
            self.error_occurred.emit(f"Could not add: {item}")
步骤 2:信号/槽连接
# view.py -- UI only, connects via signals/slots
from PyQt6.QtWidgets import QMainWindow, QVBoxLayout, QWidget, QLineEdit, QPushButton, QListWidget

class MainView(QMainWindow):
    def __init__(self, controller: Controller):
        super().__init__()
        self._controller = controller

        # Wire signals to slots
        self._controller.items_changed.connect(self._on_items_changed)
        self._controller.error_occurred.connect(self._on_error)

        # UI emits to controller -- never calls model directly
        self._add_btn.clicked.connect(lambda: self._controller.add_item(self._input.text()))

    def _on_items_changed(self, items: list) -> None:
        self._list.clear()
        self._list.addItems(items)
步骤 3:后台操作
from PyQt6.QtCore import QThread, pyqtSignal

class WorkerThread(QThread):
    progress = pyqtSignal(int)
    finished_with_result = pyqtSignal(object)
    error = pyqtSignal(str)

    def __init__(self, task_fn, parent=None):
        super().__init__(parent)
        self._task_fn = task_fn

    def run(self):
        try:
            result = self._task_fn(self.progress.emit)
            self.finished_with_result.emit(result)
        except Exception as e:
            self.error.emit(str(e))
步骤 4:QSS 主题设置
# Apply at QApplication level
app = QApplication(sys.argv)
app.setStyleSheet(Path("styles/dark-theme.qss").read_text())

# QSS file
"""
QMainWindow {
    background-color: #2b2b2b;
    color: #e0e0e0;
}
QPushButton {
    background-color: #3c3f41;
    border: 1px solid #555;
    border-radius: 4px;
    padding: 6px 16px;
    color: #e0e0e0;
}
QPushButton:hover {
    background-color: #4c5052;
}
"""
步骤 5:布局管理
# Use layout managers -- never setGeometry() or move()
layout = QVBoxLayout()
layout.addWidget(self._toolbar)
layout.addWidget(self._content, stretch=1)  # stretch fills available space
layout.addWidget(self._status_bar)

# For responsive grids
grid = QGridLayout()
grid.addWidget(label, 0, 0)
grid.addWidget(input_field, 0, 1)
grid.setColumnStretch(1, 1)  # input stretches, label stays fixed

互补技能

技能关联
modern-python使用 uv、ruff、ty、pytest 设置项目
python-backend-expert桌面应用后端的后端服务模式
tddQt 控件测试的测试驱动开发
accessibility适用于桌面应用的无障碍审查模式

记忆协议(强制)

开始前:

阅读 .claude/context/memory/learnings.md,了解此前的 PyQt6 模式和平台特定解决方法。

完成后: 将任何平台特定的渲染问题、信号/槽模式或 QThread 注意事项记录到 .claude/context/memory/learnings.md。

假设会发生中断:你的上下文可能会重置。如果没有记录在记忆中,就等于没有发生。

评论

支持 Markdown