WPFでコードビハインドを軸とした設計を考える。

コンピュータ

はじめに

この設計スタイルは、ツール・ユーティリティ系アプリの作成を想定してデザインしています。
開発規模は個人向けです。

XAMLの役割

やること

  • 画面構造の定義

    • Grid / StackPanel / DockPanel など

  • コントロール階層の定義

  • 見た目の定義

    • 色、マージン、フォント、サイズ

  • Style / Template の定義

  • コンテナ系コントロールのデータ表示

    • ItemsSource

  • 選択状態の同期

    • SelectedItem

    • SelectedIndex

  • XAML 内で完結するデータバインディング

  • 表示専用の Converter


出来るが、やらないこと

  • UI 振る舞いロジックの記述

  • 状態遷移の制御

  • 処理フローの分岐

  • ビジネスロジックの記述

  • コマンド実装の集中管理

  • 画面全体の状態をバインディングで制御

  • トリガー多用による疑似ロジック化

  • 複雑な MultiBinding

  • ViewModel を前提とした設計依存

  • XAML 側でのイベント処理完結

ViewState クラスの役割

やること

  • アプリ(ウィンドウ)の状態を管理するプロパティを集約する

    • 表示状態

    • 選択状態

    • 一時的な UI 状態

  • INotifyPropertyChanged を実装する

  • データバインディングのソースになる(ことが出来る)

  • XAML とコードビハインドから参照可能な状態コンテナとして振る舞う


やらないこと

  • コマンドの定義

    • ICommand を実装しない

    • RoutedCommand を保持しない

  • メソッドによる処理の実装

  • 画面操作ロジックの記述

  • 状態遷移の判断

  • 他オブジェクトとの連携制御

  • ビジネスロジックの保持


補足

ViewState は 振る舞いを持たない純粋な状態オブジェクトであり、

  • 値を保持する

  • 変更を通知する

以上の役割に限定します。

処理の起点は常にコードビハインド側にあり、
ViewState はその結果を反映するための 受動的なデータ構造として扱います。

Helper の役割

やること

  • static class として定義する

  • WPF に依存するメソッドを集約する

  • コントロールの振る舞いを提供する

  • UI 実装都合の状態を保持する場合がある(ConditionalWeakTable)

やらないこと

  • グローバル状態の管理

  • アプリ状態の管理(ViewState の代替)

  • ビジネスロジックの実装

  • 状態遷移の判断

Utilities の役割

やること

  • static class として定義する

  • WPF に依存しない処理をまとめる

  • 純粋な C# ロジックを提供する

  • UI とは無関係な共通処理を集約する

  • どのアプリケーション層からでも利用可能な機能を提供する


想定される内容

  • ファイル操作・パス処理

  • 文字列処理

  • 日付・数値変換

  • 設定ファイル読み書き

  • ログ出力

  • コマンドライン引数解析

  • 計算処理

  • データ変換ロジック


やらないこと

  • WPF 型への依存

    • Window

    • DependencyObject

    • Dispatcher など

  • UI 操作

  • バインディング関連処理

  • イベント・入力処理

  • 表示状態の管理

  • ViewState や Helper の代替


補足

Utilities は 最下層のロジック層に位置します。

  • UI 技術に依存しない

  • 再利用性が高い

  • 単体テストが容易

という特徴を持ちます。


位置関係

Utilities
Helper(WPF依存)

Code-behind

XAML

Utilities は、上位層から一方向に呼び出されるのみで、
UI 側の存在を一切知りません。

RoutedCommand の扱い

基本方針

  • ICommand を直接使用しない

  • WPF 標準の RoutedCommand / RoutedUICommand を使用する

  • コマンドは「処理の抽象化」ではなく 入力配線の仕組みとして扱う


やること

  • RoutedCommand を UI 入力の受け口として使用する

  • CommandBinding に処理を定義する

  • KeyBinding / InputBinding と組み合わせて使用する

  • 実際の処理はコードビハインドのメソッドに委譲する

  • 必要に応じて Helper 経由で生成・登録する


やらないこと

  • ICommand 実装クラスを作成しない

    • RelayCommand

    • DelegateCommand

    • ViewModelCommand

  • コマンドをロジック単位として扱わない

  • コマンドに処理内容を持たせない

  • ViewModel にコマンドを集約しない(ViewModelがいないので)

  • コマンドの再利用性を設計しない

 

コードビハインドの役割

やること

  • 画面単位の制御を担当する

  • UI 操作を起点とした処理の流れを記述する

  • ユーザー操作と処理ロジックを結び付ける

  • ViewState の値を更新する

  • Helper を呼び出して WPF 固有処理を実行する

  • Utilities を利用して非 UI ロジックを実行する

  • RoutedCommand の実行先を実装する

  • 初期化・終了処理を記述する


    具体的な責務

    • ボタン・メニュー・ホットキーなど入力の受信

    • 処理順序の制御

    • 画面状態の切り替え

    • 非同期処理の開始・完了制御

    • 例外のハンドリング

    • UI 更新タイミングの調整

    この設計のメリット

    • Helper と Utilities が継続的に増えていく

      • 一度書いた処理を次のアプリでもそのまま使える

    • WPF 固有処理と非依存処理が自然に分離される

      • Helper:WPF 依存

      • Utilities:WPF 非依存

    • 再利用単位が明確

      • Window や ViewModel ではなく
        メソッド単位・機能単位で再利用できる

    • 新しいアプリを作るたびに初期実装が減る

      • ドラッグ&ドロップ

      • ホットキー

      • ファイル選択

      • 表示制御

    • コードビハインドは薄く保たれる

      • 実装の多くが Helper / Utilities 側に移動する

    • アプリごとの差分が見えやすい

      • 「何をするか」だけがコードビハインドに残る

    • ライブラリとして切り出しやすい

      • Helper / Utilities は独立性が高い

    • 開発速度が継続的に向上する

      • 新規アプリほど楽になる構造

    • 技術的負債になりにくい

      • フレームワーク依存が少ない

      • MVVM 固有構造に縛られない


    要点

    • コードビハインドは「使う側」

    • Helper / Utilities は「資産側」

    という役割分担になる。

    アプリが増えるほど、

    新規実装量 ↓
    再利用コード ↑

    となり、開発は段階的に楽になっていく。

    コメント