WPF Prism episode: 16 ~ Prism7.2、ダイアログは IDialogService でって言ったよね! ~

← 前回記事【WPF Prism episode: 15 ~ FolderBrowserDialog の為ならば、Prism の InteractionRequest はもしかしたらコモンダイアログも Popup できるかもしれない。 ~】

前回の episode: 15 で WPF Prism episode シリーズは一旦完結と書きましたが、Prism 7.2 が 2019/7/25 未明にリリースされ episode: 12 ~ 15 で紹介したメッセージボックスやダイアログウィンドウを表示する PopupWindowAction に変わって IDialogService が新規に追加され、ダイアログウィンドウの表示方法が大きく変わったので Prism 7.2 の新機能も紹介することにしました。

尚、この記事は Visual Studio 2019 Community Edition で .NET Framework 4.8 以上 と C# + Prism 7.2 + ReactiveProperty を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。

Prism 7.2 の新機能

Prism 7.1 まではメッセージボックスやダイアログウィンドウ(別ウィンドウ)の表示に InteractionRequest と PopupWindowAction を使用していましたが、Prism 7.2 以降では IDialogService を使用するよう変更されました。
そして IDialogService の追加に併せて InteractionRequest と PopupWindowAction は Obsolete でマーキングされコンパイル時に警告が表示されるようになったため「廃止(非推奨)」になりました

InteractionRequest と PopupWindowAction の「廃止」と IDialogService の導入で Prism の INotification、IConfirmation から表示していた Prism 内臓のメッセージボックスも「廃止」されたためメッセージボックス等は個々で作成しなければならなくなりました。

fig.1 Prism 7.1 まで含まれていた確認メッセージボックス

確かに fig. 1 のように Prism 組み込みのメッセージボックスはイケてるとは言えないでしょうし、Prism メンテナーの Brian Lagunas もリリースノートで以下のように述べています。

The idea here is that Prism will no longer provide any built-in dialogs like Notification or Confirmation. Mainly because the Prism implementations are UGLY and will never match the styling of your beautiful WPF application. So, it’s important that you are able to register your own dialogs.

 

【機械翻訳】
ここでの考えは、Prism が通知や確認のような組み込みのダイアログをもう提供しないということです。主な理由は、Prism の実装は醜く、あなたの美しい WPF アプリケーションのスタイルとは決して一致しないからです。だから、あなたはあなた自身のダイアログを登録できることが重要です。Prism 7.2.0.1367 リリースノート

以降では Prism 7.2 から追加された IDialogService を使用してダイアログウィンドウ(メッセーボックス)を表示する方法を紹介します。

Prism 7.2 の IDialogService からメッセージボックスを表示する。

このエントリでは、今まで紹介してきたサンプルアプリではなく Prism Template Pack から新規の Prism Blank App と Prism Module を 1 つずつ作成して新規ソリューションを作成します。
新規ソリューションの作成については episode: 3 で紹介しているのでそちらを見てください。

今回の Prism 7.2 リリースに Prism Template Pack のアップデートは含まれないようなので episode :3 で紹介した手順の『Nuget パッケージの復元』実行後、『ソリューションの Nuget パッケージの管理』から Prism 7.2 へ手動でアップデートしないと IDialogService が含まれる Prism 7.2 は使用できないので注意してください。

今回作成した Module プロジェクトは、どんなプロジェクトでも汎用的に使用できるメッセージボックス Module としたいので別プロジェクトとして作成します。

メッセージボックス用の View を作成する

作成した Module プロジェクトに自動で作成される ViewA は削除して、新たに追加した Prism UserControl(WPF)をメッセージボックス用の View として scr. 1 のように作成します。

src. 1 の XAML は余白等を管理人好みに調整していますが、メッセージ表示用の TextBlock と、はい・いいえボタンを 1 つずつ配置しただけの何の変哲もない View です。

scr. 2 はメッセージボックス View 用の VM です。

Prism 7.2 以降のメッセージボックスやダイアログ用に作成する VM は 9 行目のように Prism.Services.Dialogs.IDialogAware を継承する必要があります

そして src. 3 の 18 行目のようにダイアログ用の View であることを Prism へ登録するために IModule や PrismApplication の RegisterTypes で Prism 7.2 から追加された RegisterDialog を View と VM の Type を指定して呼び出します

このサンプルはメッセージボックスを別プロジェクトに分けているので、IModule.RegisterTypes で RegisterDialog を呼び出していますが、Shell にダイアログ用の View を作成した場合は PrismApplication.RegisterTypes へ記述します。

IDialogService からダイアログウィンドウを呼び出す

そして作成したダイアログを呼び出すには src. 4 のように IDialogService.ShowDialog メソッドを使用します。

ダイアログを表示するための IDialogService は 58 行目のように Shell のコンストラクタへ DI コンテナからインジェクションしてもらいます。
※ ここでは【ダイアログ表示ボタン】の処理に ReactiveCommand を使用していますが、Prism の DelegateCommand を使用した場合も変わりません。

インジェクションされた IDialogService の ShowDialog メソッドへ表示するダイアログ View のクラス名(第 1 パラメータ)、ダイアログへ渡すパラメータ(第 2 パラメータ)、ShowDialog の戻り値を処理するコールバック(第 3 パラメータ)を指定して呼び出すと fig. 2 のようにメッセージボックスが表示されます

fig. 2 メッセージボックスの表示

ShowDialog メソッドの第 1 パラメータは仮引数名が【name】なので最初は何を指定すれば良いか分からず管理人は 10 分程悩みましたが、src. 3 で RegisterDialog する際に指定した View 名を文字列で指定します。
IDialogService からは複数のダイアログを表示することができるので、どのダイアログを表示したいか指定しないと IDialogService 側では分からないので指定するのは当然ですね。

ShowDialog メソッドは episode: 12 で紹介した InteractionRequest<INotification>.Raise メソッドと似た構文なので、以前に InteractionRequest を使用したことがあれば悩むことは無いと思います。

ここでは ShowDialog メソッドの第 2 パラメータへ null を指定していますし、メッセージボックスのボタンに何の処理も記述していないので空のメッセージボックスが表示されるだけですが、Prism 7.1 までの InteractionRequest と PopupWindowAction を組み合わせる方法と比べて非常にシンプルな設計で作成されています。

ダイアログを制御する IDialogAware

作成したダイアログをメッセーボックスとして使用するためにはダイアログ用の VM が継承している IDialogAware へ src. 5 でハイライトした部分を追加します。

問い合わせメッセーボックスに必要なのはパラメータで渡されるメッセージ文字列の表示と、はい・いいえボタンの Command を処理するだけなので、OnDialogOpened イベントで表示メッセージの設定と、両ボタンの Command で RequestClose を呼び出す処理を追加します。
これは問い合わせメッセーボックスだけでなく、通知メッセーボックスや複雑なダイアログウィンドウを表示する場合も記述は変わりません。
※ src. 5 ではデータバインディングを ReactiveProperty で処理していますが、Prism の BindableBase を使用する場合も同じように記述できます。

拡張メソッドを追加してダイアログを表示する

又、src. 4 では IDialogService.ShowDialog を MainWindow の VM で呼び出していますが、src. 6 のような拡張メソッドを作成すれば src. 4 の  showInformationMessage を書く必要が無くなります。

src. 6 の拡張メソッドを追加するとメッセーボックス呼び出し側は src. 7 のようになります。

IDialogService を DI コンテナからインジェクションする必要があるのは変わりませんが、src. 4 と比べて更にシンプルになり、Window Form のメッセージボックスとほとんど同じような呼び出しができるようになります。
fig. 3 は実行時の動作イメージです。

fig.3 パラメータで渡した内容を表示したメッセーボックス

ダイアログウィンドウのプロパティ設定

episode: 12 でも書きましたが、fig. 3 をよく見るとこのメッセージボックスは最大化・最小化もリサイズもできますし、Prism 7.1 までの PopupWindowAction と同じくタスクバーにも表示されてしまいます。
PopupWindowAction ではメッセージボックスを表示する Window 又は UserControl へ Style タグを追加してプロパティを設定していましたが、Prism 7.2 からはダイアログ用の View(UserControl)から設定できる添付プロパティが追加されています。

リサイズ不可・タスクバーへ非表示のメッセージボックスを表示するには src. 8 の Style を追加します。

src. 8 の 3 つの Style を設定すると、fig. 4 のようにリサイズできず、最大化・最小化ボタンも表示されない所謂メッセージボックスらしいメッセージボックスが表示されるようになります。

fig.4 リサイズ不可のダイアログ

このサンプルを書いた時の管理人の失敗談ですが、4 行目の『prism:Dialog.WindowStyle』をキーボードから入力する時『dialog』まで入力して IntelliSense の候補に表示される『prism:DialogWindow』を間違えて選択してしまってビルドエラーが消えなくて困りました。
Prism 7.2 でダイアログウィンドウの Style を設定する際に『プロパティ ‘Contents’ が複数回指定されています。』と言うようなビルドエラーが表示される場合はタグの打ち間違いなのでよく見直してみてください。

メッセージボックスの Style に必須と言えるのは src. 8 で挙げた 3 つですが、他の Window プロパティも設定できるようなので試してみてください。

まとめ

Prism 7.1 までの InteractionRequest と PopupWindowAction に比べてダイアログ用に分割した Module 内で記述を完結できるため、より依存度が下がる構造になりました。

確かにわざわざメッセージボックス用の画面(UserControl)を作成する手間はありますが、ここで紹介した方法で Module へ分割すれば他のプロジェクトへ簡単に使い回すことができるのでメリットは十分にあると言えます。

メッセーボックスを表示したいだけであれば手間は増えますが、複雑なダイアログウィンドウを表示したい場合等は手間がかかっても余りあるメリットがあると思いますし、IDialogService を使うためだけに Prism 7.2 にバージョンアップする価値はあると思います。

とりあえず Prism 7.2.0.1367 のリリースノート に書かれている IDialogService についての使用法を追検証しただけですが、Prism 7.2 には他にも新機能が追加されています。

管理人の残業が増えていて追加機能の検証に手を付ける時間がなかなか取れない状況なので次回エントリの予告は書けませんが、紹介したい内容があれば書こうとは思っています。

そしていつもの通り今回紹介したソースコードも GitHub リポジトリ に上げています。

 

 

あわせて読みたい

11件のフィードバック

  1. かりまた より:

    はじめまして、いつも参考にさせてもらっています。
    ありがとうございます!

    一通り目を通したつもりですが見当たらなかったので質問させてください。
    RegisterForNavigationで幾つかのページを宣言した際に、初回起動時に全てもしくは指定したページのインスタンス生成をすることは可能でしょうか?

    背景としては、VM側でファイルウォッチャーを作成しており、該当のページまで一度遷移しないとVM自体がインスタンス生成されないのでページ遷移するまでフォルダ監視が開始されない事で悩んでいます。最初に起動されるページに全てのファイルウォッチャーを起動するように書いてもよいのですが、責務的にどうなの?と思ってしまうので、初回起動時に全てのページのインスタンス生成がしたいのですが、可能でしょうか。

    • 沖田玲朗 より:

      読んで頂いてありがとうございます!

      RegisterForNavigationでViewのインスタンスを登録する方法は無さそうです。
      Viewのインスタンス生成はIRegionManager.RequestNavigateで行われるため、裏技的な方法であればShellのLoad時に登録済みViewを全てLoadする方法があるにはあります。

      方法としてはShellのLoadイベントで非表示のContentControlへ仮にRequestNavigateする方法です。
      GitHubリポジトリへサンプルを上げました。
      Prism7.2 + ReactivePropertyで書いています。

      ただ、管理人的にサンプルの方法はあまりお勧めしません。
      かりまたさんが作成されているアプリの構造が分からないのでズレた回答になるかもしれませんが、VMとファイルウォッチャーの生存期間が違っているのであれば分離した方が良いのではないでしょうか?
      今の構造だとファイルウォッチャーがVMに依存しているのでPrismの設計思想に合ってないように感じます。

      • かりまた より:

        ありがとうございます!
        意図した通りに動きました!

        >VMとファイルウォッチャーの生存期間が違っているのであれば分離した方が良いのではないでしょうか?
        言われてみれば、確かに違いますね…。
        ・特定のフォルダ以下のファイルに更新があった際にページAのデータグリッドに追加
        ・クリップボードに変化があればページBのデータグリッドに追加
        というUIへの処理をしているので、各ページにファイルウォッチャーを記述するのはしょうがないのかな?と思ってます。

        あとは、ページに4000枚程度の画像をバインドしていたり、前回情報のローディングに5秒かかっています。
        そうした場合に、初回起動時に全てのVMのインスタンス生成をしていれば、ナビゲーション時にカクつかなくて非常に嬉しくなりました。

      • かりまた より:

        立て続けにすみません。
        RequestNavigateで遷移する先を UserControl ではなく、Windowに画面を切り替える事は可能でしょうか。

        背景としては、クリップボードの監視ライブラリを使用したいと考えておりますが、UserControlには適用できず、Windowに対してのみ適用できるようなので、Windowに遷移したいと考えています。
        https://github.com/Bassman2/WpfClipboardMonitor/wiki

        調べてみますと、クリップボードの監視をするにはWindowHandle が必要で、UserControl には持っていないようです。
        前回の質問と同様に、MainWindowで監視したとしても、その情報をRequestNavigateの遷移先に反映する為には遷移先内で処理を発火させる必要があると認識している為、Windowに遷移したいと考えています。

        • 沖田玲朗 より:

          2つのコメントにまとめて返信します。
          > RequestNavigateで遷移する先を UserControl ではなく、Windowに画面を切り替える事は可能でしょうか。
          Windowに遷移と言うとダイアログウィンドウを表示したいと言う事でしょうか?
          Prismの場合ShellであるWindowは常に表示されていて内部へ表示するView(UserControl)をRequestNavigateで切り替えるので、『Windowへの遷移』がどのような状態を指すのか分かりません…

          コメントを読む限りですが、何となくVM層とモデル層が切り分けられてないように感じられるので、管理人ならこんな感じの構造にするかな…?的なサンプルを作ってGitHubリポジトリに上げました。

          サンプルとしては4プロジェクト構成のソリューションなので結構大きめのサンプルになっています。
          サンプルが必ず正しい!と言う訳ではありませんが、MVVMで作成するのであればVMは薄めにしてモデルに処理を集めるのが定石かと考えています。
          クリップボードの監視まで導入する余裕はなかったのでMainWindowに配置したボタンClickで代用していますが、ClipboardMonitorのサンプル通りCommandで受け取れるなら変わらないはずです。
          サンプルのようにViewの中継にモデルを挟む形で作成すればRequestNavigateは単純に遷移先のViewを指定するだけで良くなると思います。
          かりまたさんがReactivePropertyを使用しているかは分かりませんが、ReactiveProperty無しだともっとコードを書く必要があるかもしれません。
          管理人はReactiveProperty無しの書き方を知らないのですいません…

          サンプルの言い訳になりますがFilesystemWatcher画面へのRequestNavigateをMainWindowではなくView側で行っているのは苦肉の策で、FilesystemWatcherのイベントがUIスレッドとは別のスレッドで動くのでRequestNavigateに失敗するからです。
          そのためView側で定義しているReactiveCollectionのSubscribeで呼び出す苦肉の策を採りました。
          本来であればRequestNavigateはMainWindowへまとめた方が分かり易いと思いますが、色々試してみましたがサンプルの形でしか画面遷移が成功しませんでした。

          又、DataGridは使ったことも調べたことも無いのでズレているかもしれませんが、DataGrid(ListBox)を仮想化して画像は非同期で読み込む等すればViewのLoad時間も減ると思います。

          参考になれば幸いです。

          • かりまた より:

            PrismのTipsとして1記事書けるレベルのサンプルソースを提示して頂きありがとうございます。
            また、管理人様の天才的な発想に感謝致します。

            ファイルウォッチャーに関して、
            ① ISampleAppData を持った SampleAppData クラスを作成
            ② DIコンテナに ISampleAppData を突っ込んでおく
            ③ ISampleAppData を コンストラクタインジェクションで使いまわす
            ④ SampleAppData のコレクションに変化があった際に発火するイベントハンドラをVMに作成する
            ということですね。

            無事に実装出来ました。ありがとうございます!!

          • 沖田玲朗 より:

            かりまたさん
            参考になったのなら良かったです。

            公開したサンプルの構造は管理人の発想ではなく Prism + ReactiveProperty でMVVMなアプリを作る場合の定石の手法です。
            管理人の発想と言えるのはクリップボード監視Viewが表示されている場合にファイルウォッチャーの通知を受けてRequestNavigateする部分だけです。

            PrismのDIはサンプルのような構造を組み立てるために使用し、更新したModelのデータをView側へ通知するためにReactivePropertyを使用するとView + VM とモデル間の責務が分離しやすくなりテストコードも書きやすくなります。
            MVVMの構造の紹介にはWPF Prism episodeシリーズで紹介したサンプルより今回作ったサンプルの方が向いていそうなので今回作成したサンプルを使っていつか記事を書いた方が良いかもと考えたくらいです。

            今回のサンプルですが、初回表示がファイルウォッチャーの結果画面なので正常に動作していますが、クリップボード監視Viewが初回表示の場合は、監視フォルダに変更があってもファイルウォッチャーViewに切り替わらないはずなので、何か手段を講じる必要があるかもしれません。

  2. NIM より:

    早速の情報ありがとうございます。大変参考になります。
    ep 15 のような、コモンダイアログをIDialogServiceで表示する方法検討してます。
    もし何か方法あればぜひ記事化お願いします

    • 沖田玲朗 より:

      コメントありがとうございます。
      リクエストまで頂けると記事を書くモチベーションになるのでありがたいです。
      一応、次回 ep: 17 はコモンダイアログを表示するネタで書こうと思っていますが、最短でも公開はお盆明けになりそうです…

      ただ、ep: 17 で書こうと思っている内容は IDialogService を使用するのではなく単純な Service クラスからコモンダイアログを表示する、 Prism 7.1 でも実装できる内容になりそうです。
      特に隠すようなことでもないので書いちゃいますが、Prism GitHub の IDialogService の Issue に書かれている方法を紹介しようと思っています。
      https://github.com/PrismLibrary/Prism/issues/1666
      ↑ ここで gojanpaolo さんがリプしている実装を紹介予定です。

      • NIM より:

        返信ありがとうございます。

        私もgithubのIssue追ってましたがいまいちよくわからなかったので、記事お待ちしております!

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

%d人のブロガーが「いいね」をつけました。