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

# はじめに
今までのPrismはダイアログや別窓を表示しようと思ったら、使用するUserControlのxaml内に[InteractionRequestTrigger][]と[PopupWindowAction][]を指定して[InteractionRequest](https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Interactivity/InteractionRequest/InteractionRequest.cs)をRaiseする、という流れが必要でした。
それに取って代わる機能として[IDialogService][]という機能が実装されていたため、preの段階で気が早いとは思いますが試してみたので投稿します。

# 環境
+ .NET Framework 4.7.2
+ Prism.7.2.0.1038-pre
+ ReactiveProperty 5.3.2
+ [実装したサンプル][]




# 実装方法とポイント
[GitHubのIssue](https://github.com/PrismLibrary/Prism/issues/1666)に詳細が載っています。[実装したサンプル][]にも実際に動作するコードがあるので参考にしてください。

## 表示したいViewとViewModelを用意
ダイアログとして表示したいViewをユーザーコントロールで用意します。対になるViewModelも用意します。[実装したサンプル][]の例でいうと
[/Views/CustomDialogView.xaml](https://github.com/kwhrkzk/Prism7Sample/blob/master/IDialogServiceSample/Views/CustomDialogView.xaml)と[/ViewModels/CustomDialogViewModel.cs](https://github.com/kwhrkzk/Prism7Sample/blob/master/IDialogServiceSample/ViewModels/CustomDialogViewModel.cs)になります。

### ViewModelで[IDialogAware][]または[DialogViewModelBase][]を継承する
[DialogViewModelBase][]は[BindableBase][]と[IDialogAware][]を継承したものになります。サンプルは[DialogViewModelBase][]を使用せず[IDialogAware][]を継承して実装しました。

ウィンドウがオープンされた際に呼ばれるOnDialogOpenedや画面を閉じる際に叩くRequestClose、閉じられる際の後処理を行うOnDialogClosedなどが用意されています。
画面遷移の際に使用する[INavigationAware][]のような感じで実装できました。

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


```C#
//containerRegistry.RegisterDialog<CustomDialogView>(nameof(CustomDialogView));
containerRegistry.RegisterDialog<CustomDialogView, CustomDialogViewModel>();

```

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

## 呼び出したい場所で[IDialogService][]を注入してShow(ShowDialog)する
[実装したサンプル][]だと[/ViewModels/ContentViewModel.cs](https://github.com/kwhrkzk/Prism7Sample/blob/master/IDialogServiceSample/ViewModels/ContentViewModel.cs)のコンストラクタ付近になります。
ボタンを押されたらダイアログをShowDialogする部分を抜粋します。

```C#
        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に毎回[InteractionRequestTrigger][]と[PopupWindowAction][]を指定する必要がありましたが[IDialogService][]だとその必要がありません。

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

+ OnDialogOpenedとOnNavigatedTo
+ OnDialogClosedとOnNavigatedFrom

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

## 入力値と出力値が分かれている
今までダイアログと値のやり取りをするときは[INotification][]を継承したクラスを用意して入力したい情報も出力したい情報も一緒くたにプロパティとして指定していました。
[IDialogService][]の場合は入力値は[DialogParameters][]に値を詰めて渡します。
[RequestNavigate](https://github.com/PrismLibrary/Prism/blob/2ecbf13ed59b288d8d88446b535780bdc211b6e1/Source/Wpf/Prism.Wpf/Regions/INavigateAsync.cs)の[NavigationParameters][]のような使用感です。(実際[DialogParameters][]は[NavigationParameters][]を継承していました)
出力値は[IDialogResult][]で返ってきます。[IDialogResult][]のプロパティに[DialogParameters][]があるので出力値も自由に設定できます。

## IconSourceを設定しないといけない
[IDialogAware][]にはIconSourceというプロパティがあり、[DialogWindow.xaml](https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Services/Dialogs/DefaultDialogs/DialogWindow.xaml)にバインディングされています。アイコンなどはスタイルで一括で指定すれば良いような気がするのですが、個別の画面でそれぞれ変えたい要望があるのでしょうか。
自分的には今回は必要なかったのでIconをバインディングしないダイアログ用にWindowを用意してDefaultのWindowと差し替えました。
[/Views/CustomDialogWindow.xaml](https://github.com/kwhrkzk/Prism7Sample/blob/master/IDialogServiceSample/Views/CustomDialogWindow.xaml)

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

```C#
containerRegistry.RegisterDialogWindow<CustomDialogWindow>();
```

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

## DialogService.Show(Dialog)で引数省略できない
[DialogService.Show(Dialog)][DialogService.cs]の引数は

+ string name
+ IDialogParameters parameters
+ Action<IDialogResult> callback

の三つなのですがcallbackが必要ないときでも指定が必要です。parametersも不要なこともあるはずなので[オプション引数](https://ufcpp.net/study/csharp/sp4_optional.html#optional)もしくはオーバーロードがあると嬉しいですね。


## ShowしてShowDialogするとメインウィンドウが奥に行く
言葉で説明するのが難しいんですが、[実装したサンプル][]ではダイアログ上で同じダイアログを呼ぶような再帰的な動作を実装しました。その際に

1. メインウィンドウからダイアログをShow
1. 表示されたダイアログから新しいダイアログをShowDialog
1. 新しいダイアログを閉じる
1. ダイアログを閉じる
1. メインウィンドウが奥に行く(最前面でなくなる)

のような挙動を行います(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][]よりかなり使いやすいです。正式なリリースがされる日を楽しみに待ってます。



[実装したサンプル]:https://github.com/kwhrkzk/Prism7Sample/tree/master/IDialogServiceSample
[IDialogService]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Services/Dialogs/IDialogService.cs
[IDialogAware]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Services/Dialogs/IDialogAware.cs
[DialogViewModelBase]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Services/Dialogs/DialogViewModelBase.cs
[BindableBase]:https://github.com/PrismLibrary/Prism/blob/2ecbf13ed59b288d8d88446b535780bdc211b6e1/Source/Prism/Mvvm/BindableBase.cs
[INavigationAware]:https://github.com/PrismLibrary/Prism/blob/2ecbf13ed59b288d8d88446b535780bdc211b6e1/Source/Prism/Navigation/INavigationAware.cs
[App.xaml.cs]:https://github.com/kwhrkzk/Prism7Sample/blob/master/IDialogServiceSample/App.xaml.cs
[RegisterDialog]:https://github.com/PrismLibrary/Prism/blob/2d68c0f063975affb81cc20393a1a00ba0eb5e65/Source/Wpf/Prism.Wpf/Ioc/IContainerRegistryExtensions.cs


[InteractionRequestTrigger]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Interactivity/InteractionRequest/InteractionRequestTrigger.cs
[PopupWindowAction]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Interactivity/PopupWindowAction.cs
[IInteractionRequestAware]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Interactivity/InteractionRequest/IInteractionRequestAware.cs
[INotification]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Interactivity/InteractionRequest/INotification.cs
[DialogParameters]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Services/Dialogs/DialogParameters.cs
[NavigationParameters]:https://github.com/PrismLibrary/Prism/blob/9ee268e8a994ec6148a1539e62170a6d8dda2343/Source/Wpf/Prism.Wpf/Regions/NavigationParameters.cs
[IDialogResult]:https://github.com/PrismLibrary/Prism/blob/388d7094980bfce89911c7c46694a678fbcfae28/Source/Wpf/Prism.Wpf/Services/Dialogs/IDialogResult.cs
[IDialogWindow]:https://github.com/PrismLibrary/Prism/blob/2d68c0f063975affb81cc20393a1a00ba0eb5e65/Source/Wpf/Prism.Wpf/Services/Dialogs/IDialogWindow.cs
[DialogService.cs]:https://github.com/PrismLibrary/Prism/blob/2d68c0f063975affb81cc20393a1a00ba0eb5e65/Source/Wpf/Prism.Wpf/Services/Dialogs/DialogService.cs