ViewModel と相互作用処理（DialogService）

## はじめに

オレオレ解釈の覚え書き　その７

引き続き相互作用処理についての内容で、Prism 7.2 から採用された DialogService を使ったダイアログ表示の実装方法をまとめます。なお、本稿の例では Prism のほか Unity DI コンテナも利用しています。（DI コンテナについての説明はここでは割愛します。）

※相互作用処理とは何かについては[前回](https://qiita.com/kawasawa/items/5237aeb8610458e9a2d2)の記事にまとめました。

## 本文

はじめに、表示するダイアログの View と ViewModel を用意しましょう。View は UserControl として生成し、対応する ViewModel は IDialogAware インターフェースを実装しておきます。

```xml:View(Dialog)
<UserControl x:Class="TestApp.Views.Dialogs.MessageDialog"
			 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
             xmlns:p="http://prismlibrary.com/"
             p:ViewModelLocator.AutoWireViewModel="True">
    <Grid Margin="20 10 20 15">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Text="{Binding Message, Mode=OneWay}"/>
        <Button Grid.Row="2" Content="OK" Command="{Binding OKCommand, Mode=OneTime}" HorizontalAlignment="Right"/>
    </Grid>
</UserControl>
```

```cs:ViewModel(Dialog)

namespace TestApp.ViewModels.Dialogs
{
    public class MessageDialogViewModel : BindableBase, IDialogAware
    {
        private string _title;
        public string Title
        {
            get => this._title;
            set => this.SetProperty(ref this._title, value);
        }

        private string _message;
        public string Message
        {
            get => this._message;
            set => this.SetProperty(ref this._message, value);
        }
        
        public ICommand OKCommand { get; }

        public event Action<IDialogResult> RequestClose;

        public MessageDialogViewModel()
        {
            this.OKCommand = new DelegateCommand(() => this.OnRequestClose(new DialogResult(ButtonResult.OK)));
        }

        public void OnDialogOpened(IDialogParameters parameters)
        {
            this.Title = parameters.GetValue<string>(nameof(this.Title)) ?? string.Empty;
            this.Message = parameters.GetValue<string>(nameof(this.Message)) ?? string.Empty;
        }

        public bool CanCloseDialog()
        {
            return true;
        }

        public void OnDialogClosed()
        {
        }

        protected void OnRequestClose(IDialogResult dialogResult)
        {
            this.RequestClose?.Invoke(dialogResult);
        }
    }
}
```

上記で定義したダイアログの View と ViewModel の型をアプリケーションのエントリーポイントで DI コンテナに登録します。これにより、後述する IDialogService インターフェースを経由して登録されたダイアログを利用できます。

```cs:App
namespace TestApp
{
    public partial class App : PrismApplication
    {
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterDialog<Views.Dialogs.MessageDialog, ViewModels.Dialogs.MessageDialogViewModel>();
        }
    }
}
```

ダイアログを表示するクラスでは、IDialogService.ShowDialog メソッドを使用し、指定されたクラス名のダイアログを表示することができます。（例では示されていませんが、戻り値を受け取ることも可能です。）

```cs:ViewModel(Window)
namespace TestApp.ViewModels
{
    public class MainWindowViewModel
    {
        [Dependency]
        public IDialogService DialogService { get; set; }

        // このメソッドを呼び出せばダイアログを表示できる
        public void ShowMessage()
        {
            var parameters = new DialogParameters {
                { nameof(MessageDialogViewModel.Title), "タイトル" },
                { nameof(MessageDialogViewModel.Message), "メッセージ" },
            };
            this.DialogService.ShowDialog("MessageDialog", parameters, null);
        }
    }
}
```

IDialogService という明らかにダイアログ表示を意識したインターフェースが ViewModel に登場するのは少々気持ち悪いですが、実体は外部から注入されており、依存関係の排除とテスタビリティの高さを兼ね備えています。また InteractionRequest とは違い View にトリガーを定義する必要がないため、ViewModel 内で処理が完結しています。

## おわりに

DialogService を使った相互作用処理についてまとめました。比較的新しい機能で情報は少ないですが、ダイアログを表示する手段がまた一つ増えました。

~~一方で InteractionRequest は汎用性が高く、ViewModel を起点に何かを発信する用途であれば、ダイアログの表示以外にも様々な用途が考えられます。これらもまた適材適所で使い分けていくことになりそうです。~~
Prism 7.2 で Obsolete が付与された InteractionRequest は次期バージョンで削除されることになりました。現在使用中の Prism をアップデートする場合はご注意ください。

次回は View からの処理依頼を受け取る委譲コマンドについてまとめます。