13
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WPFのMVVM、「理想」と「現実」のギャップを埋めるハイブリッド設計

Posted at

はじめに:MVVMの理想と、現場で直面する「つらさ」

WPFでUIを開発するとき、MVVMパターンは今やデファクトスタンダードですよね。ViewModelはViewを知らず、テストが容易で、関心の分離が美しい...。理想は素晴らしいものです。

しかし、現場で実装していると、こんな「つらさ」に直面しませんか?

  • 「すべてをデータバインドでやらなきゃ…」という制約。ダイアログ表示やアニメーションのトリガーなど、バインドだけでは煩雑になる処理がある。
  • ViewModelから能動的にViewを操作したいのに、そのタイミングをうまく制御できない。
  • IsHogeFugaTriggerのような、Viewへの通知ためだけの**無駄な「トリガープロパティ」**をViewModelに作りがち。
  • 特にCADビューアのような高性能なUIでは、データバインドの更新頻度がパフォーマンスの足かせになる。

これらの問題を解決するために、MVVMの原則を守りつつも、より現実的なアプローチとして**「ViewService」**を導入するハイブリッドな設計について紹介します。

解決策:IView / ViewServiceという「通訳」の導入

この設計の核となるのは、ViewModelとViewの間に、UI操作を代行してくれる**「通訳」役のインターフェース**を置くことです。ここではIViewServiceと呼びます。

  • ViewModelは、具体的なView(MainWindow.xamlなど)のことは知りません。
  • 代わりに、ISomeViewServiceという抽象的なインターフェースにのみ依存します。
  • ViewServiceの実装クラスが、Viewへの具体的な操作をすべて引き受けます。

これにより、ViewModelのテスト容易性というMVVM最大のメリットは維持したまま、Viewにしかできない処理を安全に呼び出せるようになります。

実装例:IMainContentViewServiceの設計

私が実際のCAD系アプリケーションで実装したViewServiceのインターフェースがこちらです。

imaincontentviewservice.cs
public interface IMainContentViewService
{
    // ViewからVMへ、必要なデータだけを公開する
    IEnumerable<IProfileDataPointElement> SelectedVertices { get; }

    // VMからViewへ、振る舞いを命令するためのコマンド
    RelayCommand FitCommand { get; }
    RelayCommand SelectAllCommand { get; }
    RelayCommand ShowSettingDialogCommand { get; }
    RelayCommand ShowGridCommand { get; }
    // ...など多数
}

ポイント:ICommandを直接公開する

void Fit()のようなメソッドではなく、RelayCommandのようなICommandプロパティを公開しているのが特徴です。
これにより、UIの組み立てをXAMLの宣言的な記述だけで完結させることができます。

例えば、メインウィンドウのメニュー項目から、このViewのFitCommandを直接バインドできます。

shellview.xaml
<MenuItem Header="フィット表示"
          Command="{Binding MainContentService.FitCommand}" />

ViewServiceの実装クラス

このインターフェースの実装は、受け取った命令をViewの各部品に伝達するだけのシンプルなものになります。

maincontentviewservice.cs
public class MainContentViewService : ViewService<MainContentView>, IMainContentViewService
{
    public MainContentViewService(MainContentView mainContentView) : base(mainContentView) { }

    // SelectedVerticesは、Viewの内部コントロールからデータを取得して返すだけ
    public IEnumerable<IProfileDataPointElement> SelectedVertices => this.View.ProfileDataTableView.SelectedItemsAsProfilePointsData;

    private RelayCommand? _fitCommand = null;
    public RelayCommand FitCommand
    {
        get
        {
            if (this._fitCommand == null)
            {
                // canExecuteのロジックは、Viewの状態に直接アクセスできるのでシンプル
                bool canExecute(object obj)
                {
                    return this.View.ProfileDataTableView.Items.Count > 0;
                }

                // executeは、Viewの特定の部品のメソッドを呼ぶだけ
                void execute(object obj)
                {
                    this.View.ProfileDataGraphView.InternalZoomViewer.Fit();
                }

                this._fitCommand = new RelayCommand(canExecute, execute);
            }
            return this._fitCommand;
        }
    }
    // ...他のコマンド実装
}

canExecuteの判定にthis.View.ProfileDataTableView.Items.Countを直接使っている点に注目してください。ViewModelにこの状態を同期させるよりも、はるかにシンプルで効率的です。

設計原則:私たちのハイブリッドMVVMルール

このアーキテクチャをうまく運用するために、私たちは以下の4つのルールを設けました。これにより、実装の際に「これはどっちで書くべき?」という迷いがなくなります。

1. 状態の同期 → データバインド

ViewModelのプロパティとViewの見た目を常に同期させたい場合は、伝統的なデータバインドを使います。これはMVVMの基本です。
(例: TextBoxTextItemsControlItemsSource)

2. 振る舞いの命令 → ViewService

ViewModelからViewへ一度きりの動作を命令したい場合は、ViewServiceのメソッドやコマンドを使います。
(例: FitToScreen(), ShowDialog())

3. View内部の処理 → コードビハインド

ViewModelが知る必要のない、Viewだけで完結するUIロジックは、コードビハインド(.xaml.cs)に書くことを許可します。
(例: ウィンドウの閉じるボタン、マウスオーバー時のアニメーション)

4. VMが知るべき情報 → 必要最低限に

ViewからViewModelへ情報を伝えるのは、その情報がビジネスロジックで本当に必要な場合のみです。UIの見た目の状態(例: パネルが開いているか)はViewModelに伝えてはいけません。

おわりに

MVVMは強力なパターンですが、その「理想」に固執しすぎると、かえって複雑で非効率なコードを生んでしまうことがあります。

今回紹介したViewServiceを導入するハイブリッドなアプローチは、MVVMの「テスト容易性」や「関心の分離」といったメリットを享受しつつ、現場での開発効率と保守性を高めるための現実的な落とし所です。

WPF/MVVMでの実装に悩んでいる方の、一つのヒントになれば幸いです。

13
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?