SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

MVVMでViewModelから別ウィンドウ表示をするサンプル~シンプルなTODOリスト~

以前、MVVMパターンで設計する際に、ダイアログ表示をどう行うか、、という内容で↓の記事のようなサンプルを書きました。

今回はコレの派生形として、任意のウィンドウをMVVMなパターンで表示するサンプルを書いてみました。

こんな感じの、よくあるTODOリストアプリです。
TODO項目の追加/編集を別ウィンドウで入力/編集する、というUIになっています。
f:id:minami_SC:20190609220554g:plain

コード一式は以下の場所に置いてます。

概要

ViewModelからダイアログ表示をするにあたり、ViewModelから直接Viewを参照するのではなく、サービスクラスを作ってこのインスタンスを外部からDIする、という基本的な概念は前回のサンプルと同じです。

今回のサンプルでは、ウィンドウ表示処理をVMから指示するために、以下のようなインターフェースを定義しました。

IShowWindowService.cs

    interface IShowWindowService<TViewModel>
    {
        bool? ShowDialog(TViewModel context);
    }

ShowWindowService.cs

    class ShowWindowService<TWindow, TViewModel> : IShowWindowService<TViewModel>
        where TWindow : Window, new()
    {
        public Window Owner { get; set; }

        public bool? ShowDialog(TViewModel context)
        {
            var dlg = new TWindow()
            {
                Owner = this.Owner,
                DataContext = context,
            };

            return dlg.ShowDialog();
        }

        public void Show(TViewModel context)
        {
            var dlg = new TWindow()
            {
                Owner = this.Owner,
                DataContext = context,
            };

            dlg.Show();
        }
    }

サービスのIFはIShowWindowService<TViewModel>ですが、それを継承したサービス実装クラスはShowWindowService<TWindow, TViewModel>という形式にしています。

IShowWindowServiceインターフェースは、VMからも参照されるものとなるので、ここにView側の要素を含めたくないため、このような形となっています。

ViewModelへのサービスのインジェクション

今回のサンプルでは、MainWindowのコンストラクタでViewModelのインスタンスを作っています。
ですので、ここでMainWindowViewModelに対し、ダイアログ表示を行うためのサービスをコンストラクタを通してDIしています。

MainWindow.xaml.cs

        public MainWindow()
        {
            InitializeComponent();

            // MainWindowViewModelに、コンストラクタ経由でIShowWindowServiceへの依存性を注入する。
            var showWindowService = new ShowWindowService<EditDialog, ToDoItemViewModel>()
            {
                Owner = this
            };
            this.DataContext = new MainWindowViewModel(showWindowService);
        }

サービスを用いた、ViewModelからのウィンドウ表示

ViewModelからは、インターフェース経由で別ウィンドウ表示を行います。
この際、ShowDialogメソッドの引数で、別ウィンドウ側でDataContextとして使用するオブジェクトを渡すようにしています。

MainWindowViewModel.cs

        private void AddItem()
        {
            // 追加要素のVMを作成する
            var newItemViewModel = new ToDoItemViewModel();
            var ret = this._showWindowService.ShowDialog(newItemViewModel);
            if (ret == true)
            {
                // ダイアログでOKが押された場合は、ToDoListに要素を追加する。
                this.ToDoList.Add(newItemViewModel);

                // 追加した項目を選択状態にする
                this.SelectedItem = newItemViewModel;
            }
        }

今回のサンプルでの積み残し

このサンプルでは、EditDialogで「OK」「キャンセル」ボタン押下でのウィンドウを閉じる処理は、View側で直接書いています。

「OK」ボタン押下時にViewから直接ウィンドウを閉じるのではなく、ViewModel側で何らかのロジックやチェック処理を挟んでからウィンドウを閉じるようにするなら、何らかの形でViewModelからViewへの通知処理を付け加える必要がありますね。

その場合は、↓のような選択肢のいずれかでしょうか。

  • Viewの要素をInterfaceを通してVMに持たせて、IF経由でViewを操作する
  • VMでイベントを作り、そのイベントをViewから購読する
  • BlendSDKのEventTriggerとかを使ってViewに通知
  • PrismのInteractionTriggerとか、MvvmLightToolkitのMessengerなどの機構を通してVMからViewに通知

まぁ、Prismとか使ってるなら、そもそもここで書いたようなサービス経由での処理を使わずとも、ViewModelから別ウィンドウを開くような手段が用意されてるので、そっちを使った方がいいのかな。

ViewModelからViewへの通知については、最近BlendSDKのEventTriggerを使った方法がお気に入りなので、また別途サンプルを書こうと思います。