Skip to content
Back to skills

Lorairo Qt Widget

ASecurity

PySide6 widget implementation for LoRAIro GUI with Signal/Slot pattern, Direct Widget Communication, and Qt Designer integration best practices

  • 666 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added February 9, 2026
developmentpythongobash

Works with

  • cli
  • mcp

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill lorairo-qt-widget --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Lorairo Qt Widget?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Lorairo Qt Widget
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-lorairo-qt-widget/badge)](https://www.skillsdirectory.com/skills/majiayu000-lorairo-qt-widget)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: lorairo-qt-widget
description: PySide6 widget implementation for LoRAIro GUI with Signal/Slot pattern, Direct Widget Communication, and Qt Designer integration best practices
allowed-tools: mcp__serena__find_symbol, mcp__serena__get_symbols_overview, mcp__serena__read_memory, Read, Edit, Write, Bash
---

# LoRAIro Qt Widget Skill

このSkillは、LoRAIroプロジェクトにおけるPySide6ウィジェット実装のベストプラクティスを提供します。

## 使用タイミング

- 新しいGUIウィジェットの実装
- 既存ウィジェットのリファクタリング
- Signal/Slot接続の実装
- Qt Designerファイル統合

## LoRAIroの Widget実装パターン

### 基本構造

```python
from PySide6.QtWidgets import QWidget
from PySide6.QtCore import Signal, Slot
from typing import Optional
from loguru import logger

class ExampleWidget(QWidget):
    """サンプルウィジェット

    Signals:
        data_changed: データ変更時に発火(新しいデータを送信)
        action_requested: ユーザーアクション時に発火
    """

    # 型安全なシグナル定義
    data_changed = Signal(str)  # str型のデータを送信
    action_requested = Signal()  # パラメータなし

    def __init__(self, parent: Optional[QWidget] = None):
        super().__init__(parent)
        self._setup_ui()
        self._connect_signals()
        self._data: Optional[str] = None

    def _setup_ui(self) -> None:
        """UI初期化"""
        # Qt Designerファイル使用時:
        # from .ExampleWidget_ui import Ui_ExampleWidget
        # self._ui = Ui_ExampleWidget()
        # self._ui.setupUi(self)

        # 手動レイアウト時:
        layout = QVBoxLayout()
        self.setLayout(layout)

    def _connect_signals(self) -> None:
        """内部Signal/Slot接続"""
        # self._ui.button.clicked.connect(self._on_button_clicked)
        pass

    @Slot(str)
    def set_data(self, data: str) -> None:
        """データ設定(外部から呼び出し可能)"""
        if self._data != data:
            self._data = data
            self._update_display()
            self.data_changed.emit(data)

    def _update_display(self) -> None:
        """表示更新(内部処理)"""
        # UI更新ロジック
        pass

    @Slot()
    def _on_button_clicked(self) -> None:
        """ボタンクリックハンドラ(プライベート)"""
        logger.debug("Button clicked")
        self.action_requested.emit()
```

## 重要な実装パターン

### 1. Direct Widget Communication

```python
# LoRAIroの推奨パターン: Widget間直接接続

class ThumbnailWidget(QWidget):
    """サムネイル表示ウィジェット"""
    image_metadata_selected = Signal(dict)  # メタデータを直接送信

    def _on_thumbnail_clicked(self, index: int) -> None:
        """サムネイルクリック時"""
        metadata = self._image_metadata[index]
        self.image_metadata_selected.emit(metadata)  # 直接送信

class ImageDetailsWidget(QWidget):
    """画像詳細表示ウィジェット"""

    def connect_to_thumbnail_widget(self, thumbnail_widget: ThumbnailWidget) -> None:
        """サムネイルウィジェットに直接接続"""
        thumbnail_widget.image_metadata_selected.connect(
            self._on_metadata_received
        )

    @Slot(dict)
    def _on_metadata_received(self, metadata: dict) -> None:
        """メタデータ受信時"""
        self._display_metadata(metadata)

# MainWindowでの接続
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self._setup_widgets()
        self._connect_widgets()

    def _connect_widgets(self) -> None:
        """Widget間接続(一箇所に集約)"""
        self.image_details.connect_to_thumbnail_widget(self.thumbnail)
```

### 2. 型安全なSignal定義

```python
# ✅ Good: 型指定付きSignal
class DataWidget(QWidget):
    # 単一パラメータ
    data_changed = Signal(str)
    score_updated = Signal(float)
    count_changed = Signal(int)

    # 複数パラメータ
    item_selected = Signal(str, int)  # (name, index)

    # 複雑なデータは dict
    metadata_loaded = Signal(dict)

# ❌ Bad: 型なしSignal
class BadWidget(QWidget):
    data_changed = Signal()  # 何が送信されるか不明
    value_updated = Signal(object)  # 型が不明確
```

### 3. Qt Designerファイル統合

```python
# UI生成コマンド
# uv run python scripts/generate_ui.py

from .ExampleWidget_ui import Ui_ExampleWidget

class ExampleWidget(QWidget):
    def __init__(self, parent: Optional[QWidget] = None):
        super().__init__(parent)

        # Qt Designer UIのロード
        self._ui = Ui_ExampleWidget()
        self._ui.setupUi(self)

        # UI要素への接続
        self._ui.okButton.clicked.connect(self._on_ok_clicked)
        self._ui.inputField.textChanged.connect(self._on_text_changed)

    @Slot()
    def _on_ok_clicked(self) -> None:
        text = self._ui.inputField.text()
        logger.debug(f"OK clicked with text: {text}")
```

### 4. 非同期処理との統合

```python
from PySide6.QtCore import QThreadPool
from src.lorairo.gui.workers.base import LoRAIroWorkerBase

class AsyncWidget(QWidget):
    """非同期処理を扱うウィジェット"""

    def __init__(self):
        super().__init__()
        self._worker_manager = None  # WorkerManagerへの参照

    def set_worker_manager(self, worker_manager) -> None:
        """WorkerManager設定"""
        self._worker_manager = worker_manager

    def start_async_operation(self) -> None:
        """非同期操作開始"""
        worker = MyAsyncWorker(data=self._data)
        worker.signals.progress.connect(self._on_progress)
        worker.signals.finished.connect(self._on_finished)
        worker.signals.error.connect(self._on_error)

        self._worker_manager.submit(worker)

    @Slot(int, int)
    def _on_progress(self, current: int, total: int) -> None:
        """進捗更新"""
        progress = int(current / total * 100)
        self._ui.progressBar.setValue(progress)

    @Slot(object)
    def _on_finished(self, result) -> None:
        """完了処理"""
        logger.info(f"Operation finished: {result}")
        self._display_result(result)

    @Slot(str)
    def _on_error(self, error_msg: str) -> None:
        """エラー処理"""
        logger.error(f"Operation error: {error_msg}")
        QMessageBox.warning(self, "Error", error_msg)
```

## LoRAIro固有のガイドライン

### ファイル配置
- **Widgets**: `src/lorairo/gui/widgets/`
- **Designer files**: `src/lorairo/gui/designer/*.ui`
- **Generated UI**: `src/lorairo/gui/designer/*_ui.py` (自動生成)
- **Window**: `src/lorairo/gui/window/main_window.py`

### 命名規則
- Class: `{Name}Widget`(例: `ThumbnailWidget`, `ImageDetailsWidget`)
- Signals: `{action}_{tense}` (例: `data_changed`, `item_selected`)
- Public methods: `set_*`, `get_*`, `update_*`
- Private methods: `_on_*` (イベントハンドラ), `_update_*` (内部処理)
- Slots: `@Slot()` デコレータ必須

### Direct Widget Communication原則
1. **中間レイヤー回避**: DatasetStateManager経由を避け、直接接続
2. **MainWindowで集約**: `_connect_widgets()` メソッドに全接続を集約
3. **connect_to_* メソッド**: 各Widgetに `connect_to_{other_widget}()` メソッド提供
4. **型安全**: Signal/Slotは型指定必須

## ベストプラクティス

### DO ✅
- **@Slot デコレータ**: 全Slotに `@Slot(型)` 必須
- **型ヒント**: 全メソッドに型ヒント
- **private/public分離**: `_` プレフィックスで明確化
- **connect_to_* パターン**: Widget間接続メソッド提供
- **ログ記録**: 重要なイベントはloguru でログ

### DON'T ❌
- **直接UI操作**: 外部から `widget._ui.button` は禁止
- **グローバル状態**: Widget内で共有状態を持たない
- **ビジネスロジック混入**: Widgetは表示とイベント処理のみ
- **長時間処理**: UI スレッドで重い処理を避ける(Worker使用)
- **暗黙的接続**: 自動接続に期待せず、明示的に `connect()`

## テスト戦略

```python
import pytest
from PySide6.QtWidgets import QApplication
from src.lorairo.gui.widgets.example_widget import ExampleWidget

@pytest.fixture
def app(qtbot):
    """Qt application fixture"""
    return QApplication.instance() or QApplication([])

@pytest.fixture
def widget(qtbot):
    """Widget fixture"""
    w = ExampleWidget()
    qtbot.addWidget(w)
    return w

def test_signal_emission(qtbot, widget):
    """Signal発火テスト"""
    with qtbot.waitSignal(widget.data_changed, timeout=1000) as blocker:
        widget.set_data("test data")

    assert blocker.args[0] == "test data"

def test_button_click(qtbot, widget):
    """ボタンクリックテスト"""
    with qtbot.waitSignal(widget.action_requested):
        qtbot.mouseClick(widget._ui.button, Qt.LeftButton)
```

## 参考リソース
- 既存Widget: `src/lorairo/gui/widgets/`
- MainWindow: `src/lorairo/gui/window/main_window.py`
- Worker Base: `src/lorairo/gui/workers/base.py`
- テスト例: `tests/gui/widgets/`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.