C#
WPF
PRISM

Prism7.2(pre)の新機能 IDialogServiceを試してみた


はじめに

今までのPrismはダイアログや別窓を表示しようと思ったら、使用するUserControlのxaml内にInteractionRequestTriggerPopupWindowActionを指定してInteractionRequestをRaiseする、という流れが必要でした。

それに取って代わる機能としてIDialogServiceという機能が実装されていたため、preの段階で気が早いとは思いますが試してみたので投稿します。


環境


実装方法とポイント

GitHubのIssueに詳細が載っています。実装したサンプルにも実際に動作するコードがあるので参考にしてください。


表示したいViewとViewModelを用意

ダイアログとして表示したいViewをユーザーコントロールで用意します。対になるViewModelも用意します。実装したサンプルの例でいうと

/Views/CustomDialogView.xaml/ViewModels/CustomDialogViewModel.csになります。


ViewModelでIDialogAwareまたはDialogViewModelBaseを継承する

DialogViewModelBaseBindableBaseIDialogAwareを継承したものになります。サンプルはDialogViewModelBaseを使用せずIDialogAwareを継承して実装しました。

ウィンドウがオープンされた際に呼ばれるOnDialogOpenedや画面を閉じる際に叩くRequestClose、閉じられる際の後処理を行うOnDialogClosedなどが用意されています。

画面遷移の際に使用するINavigationAwareのような感じで実装できました。


ダイアログをコンテナに登録

App.xaml.csのRegisterTypes内に登録します。ダイアログ専用に登録するための拡張メソッド(RegisterDialog)が追加されていました。

//containerRegistry.RegisterDialog<CustomDialogView>(nameof(CustomDialogView));

containerRegistry.RegisterDialog<CustomDialogView, CustomDialogViewModel>();

Prism.7.2.0.1038-preの時点ではViewとViewModelを明示的に指定するメソッドしか用意されていませんでしたが執筆時点で元ソースを見るとコメントアウト部のような書き方ができるメソッドが追加されていました。


呼び出したい場所でIDialogServiceを注入してShow(ShowDialog)する

実装したサンプルだと/ViewModels/ContentViewModel.csのコンストラクタ付近になります。

ボタンを押されたらダイアログをShowDialogする部分を抜粋します。

        public ContentViewModel(IDialogService _dialogService)

{
DialogService = _dialogService;

ShowDialogCommand
.Subscribe(() =>
{
IDialogResult result = null;
DialogService.ShowDialog(nameof(CustomDialogView), new DialogParameters { { "Input", Input.Value } }, ret => result = ret);

if (result != null)
Input.Value = result.Parameters.GetValue<string>("Input");

}).AddTo(DisposeCollection);
}

表示するダイアログを指定して、引き渡したいパラメータを指定して、コールバックを指定します。

ShowDialogの場合は実行するとメインウィンドウの方が止まるので地続きにかけました。

基本的な実装方法は以上です。


IDialogServiceの良い点気になった点


実装が楽

はじめにでも書きましたが、今までのPrismだとダイアログを表示したい画面のxamlに毎回InteractionRequestTriggerPopupWindowActionを指定する必要がありましたがIDialogServiceだとその必要がありません。


Region用の画面と共用するのが楽

今まで(IInteractionRequestAwareを実装するやり方)でも出来なくはなかったんですけどINavigationAwareと使用感が違いすぎてやりづらかったですがIDialogAwareINavigationAwareと似たようなインターフェースになったので両方継承しても違和感なかったです。


  • OnDialogOpenedとOnNavigatedTo

  • OnDialogClosedとOnNavigatedFrom

実装したサンプルでは共用の画面として実装してみました。


入力値と出力値が分かれている

今までダイアログと値のやり取りをするときはINotificationを継承したクラスを用意して入力したい情報も出力したい情報も一緒くたにプロパティとして指定していました。

IDialogServiceの場合は入力値はDialogParametersに値を詰めて渡します。

RequestNavigateNavigationParametersのような使用感です。(実際DialogParametersNavigationParametersを継承していました)

出力値はIDialogResultで返ってきます。IDialogResultのプロパティにDialogParametersがあるので出力値も自由に設定できます。


IconSourceを設定しないといけない

IDialogAwareにはIconSourceというプロパティがあり、DialogWindow.xamlにバインディングされています。アイコンなどはスタイルで一括で指定すれば良いような気がするのですが、個別の画面でそれぞれ変えたい要望があるのでしょうか。

自分的には今回は必要なかったのでIconをバインディングしないダイアログ用にWindowを用意してDefaultのWindowと差し替えました。

/Views/CustomDialogWindow.xaml

差し替える方法はApp.xaml.csのRegisterTypes内でRegisterDialogWindowを使用して指定します。

containerRegistry.RegisterDialogWindow<CustomDialogWindow>();

注意点として差し替えるWindowはIDialogWindowを継承している必要があります。


DialogService.Show(Dialog)で引数省略できない

DialogService.Show(Dialog)の引数は


  • string name

  • IDialogParameters parameters

  • Action callback

の三つなのですがcallbackが必要ないときでも指定が必要です。parametersも不要なこともあるはずなのでオプション引数もしくはオーバーロードがあると嬉しいですね。


ShowしてShowDialogするとメインウィンドウが奥に行く

言葉で説明するのが難しいんですが、実装したサンプルではダイアログ上で同じダイアログを呼ぶような再帰的な動作を実装しました。その際に


  1. メインウィンドウからダイアログをShow

  2. 表示されたダイアログから新しいダイアログをShowDialog

  3. 新しいダイアログを閉じる

  4. ダイアログを閉じる

  5. メインウィンドウが奥に行く(最前面でなくなる)

のような挙動を行います(ShowDialog -> ShowDialogでは発生しないし再帰しなければ問題ない)。

WindowのOwner周りの指定かと思って元ソースを確認したところ

DialogService.cs抜粋

C#:DialogService.cs

//TODO: is there a better way to set the owner

window.Owner = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.IsActive);

となっていました。1.でダイアログがshowされる際にメインウィンドウがOwnerに指定されているはずなので問題ないような気がしますが解明できていません。


まとめ

PopupWindowActionよりかなり使いやすいです。正式なリリースがされる日を楽しみに待ってます。