pyqt6-ui-development-rules について
PyQt6 のデスクトップ GUI を構築、改修、デバッグする開発者向けの開発スキルです。MVC 分離、スレッド処理、テーマ設定、クロスプラットフォームのレイアウトを扱います。
主な機能
- 01MVC 分離とシグナル・スロット通信
- 02アプリケーション単位の QSS テーマ設定
- 03QThread とスレッドプールによるバックグラウンド処理
- 04レイアウトマネージャーによるレスポンシブ設計
- 05クロスプラットフォームの DPI とアクセシビリティ対応
活用シーン
- 01新しい PyQt6 デスクトップアプリの構築
- 02既存の PyQt または PySide コードの改修
- 03画面のフリーズや応答停止の調査
- 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.mdhooks/post-execute.cjspre-execute.cjsreferences/research-requirements.mdrules/pyqt6-ui-development-rules.mdschemas/input.schema.jsonoutput.schema.jsonscripts/main.cjstemplates/implementation-template.md詳細
PyQt6 UI 開発ルールスキル
MVC の分離、シグナル/スロットアーキテクチャ、QSS によるテーマ設定、スレッドによる並行処理、クロスプラットフォームのレンダリングに関するベストプラクティスを徹底する、PyQt6 デスクトップ GUI 開発のスペシャリストです。応答性、アクセシビリティ、視覚的な一貫性を備えたデスクトップアプリケーションを実現します。 - MVC を分離した PyQt6 アプリケーションアーキテクチャを設計する - UI とビジネスロジック間のシグナル/スロットによる通信パターンを実装する - ダークモード/ライトモードに対応する、アプリケーションレベルの QSS テーマ設定を構成する - QThread、QRunnable、QThreadPool を使用してバックグラウンド処理を管理する - QVBoxLayout、QHBoxLayout、QGridLayout、QFormLayout を使用して応答性の高いレイアウトを構築する - 適切な paintEvent 処理を備えたカスタム QWidget サブクラスを実装する - クロスプラットフォームの DPI 対応レンダリングを設定する - アクセシビリティ機能(スクリーンリーダー対応、キーボードナビゲーション)を構成する概要
このスキルでは、実用レベルの PyQt6 デスクトップアプリケーションを構築するためのルールを徹底します。中心となる原則は、シグナル/スロットによる厳格な MVC の分離、UI スレッドを決してブロックしないこと、QSS による一元的なテーマ設定、レイアウトマネージャーを活用した応答性の高い設計です。これらのルールにより、UI のフリーズ、テスト困難な結合、プラットフォーム固有のレンダリングバグなど、PyQt6 でよく発生する問題を防ぎます。
使用する場面
- 新しい PyQt6 デスクトップアプリケーションを構築するとき
- 既存の PyQt/PySide コードを PyQt6 にリファクタリングするとき
- Qt UI のフリーズや応答停止をデバッグするとき
- カスタムウィジェットや複雑なレイアウトを実装するとき
- クロスプラットフォームのデスクトップアプリケーションのビルドを設定するとき
絶対に守るべきルール
- 必ず UI からロジックへの通信には Qt のシグナル/スロット機構を使用してください。UI 層とビジネスロジック層の間でメソッドを直接呼び出すと、MVC の分離が損なわれ、テスト困難な結合が生じます。
- 決して メイン UI スレッドで時間のかかる処理を実行しないでください。Qt のイベントループをブロックすると、インターフェースが応答しなくなり、OS の「応答なし」ダイアログが表示されます。
- 必ず QSS スタイルシートはウィジェットごとではなく、QApplication レベルで適用してください。ウィジェットごとのインラインスタイルは、テーマに一貫性がなく、保守困難なスタイル設定の乱立を招きます。
- 決して ウィジェットのレイアウトに絶対的なピクセル座標を使用しないでください。DPI 対応およびクロスプラットフォームのレンダリングを実現するため、Qt のレイアウトマネージャー(QVBoxLayout、QHBoxLayout、QGridLayout)を使用してください。
- 必ず リリース前に対象となるすべてのプラットフォームで UI をテストしてください。PyQt6 のレンダリング、フォントの拡大縮小、ウィジェットのサイズは、Windows、macOS、Linux で異なります。
アンチパターン
| アンチパターン | 失敗する理由 | 正しい方法 |
|---|---|---|
| UI スロットからビジネスロジックを直接呼び出す | UI とロジックが密結合になり、テストが不可能になり、MVC アーキテクチャが崩れます | UI からシグナルを発行し、スロット経由でコントローラー/サービスのメソッドに接続します |
| メインスレッドでネットワークやファイルの I/O を実行する | Qt のイベントループをブロックし、処理が完了するまで UI がフリーズします | バックグラウンド処理には QThread、QRunnable、または qasync を使った asyncio を使用します |
| ピクセルサイズや位置をハードコードする | 高 DPI ディスプレイや OS ごとに異なる DPI スケーリング設定でレイアウトが崩れます | レイアウトマネージャーとサイズポリシーを使用し、DPI に応じたサイズ設定には logicalDpiX() を使います |
| 個々のウィジェットにスタイルをインラインで設定する | 見た目に一貫性がなくなり、テーマ設定やメンテナンスが非常に困難になります | 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 | デスクトップアプリのバックエンド向けバックエンドサービスのパターン |
tdd | Qt ウィジェットのテストにおけるテスト駆動開発 |
accessibility | デスクトップアプリに適用できるアクセシビリティ監査のパターン |
メモリプロトコル(必須)
開始前:
過去の PyQt6 のパターンやプラットフォーム固有の回避策について、.claude/context/memory/learnings.md を読みます。
完了後: プラットフォーム固有の描画上の問題、シグナル/スロットのパターン、または QThread の注意点があれば、.claude/context/memory/learnings.md に記録します。
中断されることを想定してください。コンテキストがリセットされる場合があります。メモリに記録されていなければ、起きていないのと同じです。
コメント
Markdown 対応