ReactivePropertyとMaterial Design In XAML Toolkitを組み合わせて応答を待つダイアログを作る

# 概要
MVVMでつまずくポイントとしてよく挙げられるのが、ダイアログ表示です。
単純にダイアログを表示して、OKを押されたら閉じる、という内容を以前に書きました。
[WPFでいけてるダイアログを出す方法 - Qiita](https://qiita.com/soi/items/e322d60d22af6fcd7da9#viewmodel%E3%81%8B%E3%82%89-1)
今回はダイアログが閉じられるのを待って結果を受け取る方法を
Material Design In XAML ToolkitとReactivePropertyを使用して説明したいと思います。
なお今回の内容は現時点（2018/08/26）で最新のReactiveProperty 5.2.0が必要です。

# 説明
ダイアログはMaterial Design In XAML ToolkitのDialogHostを使用して表示します。
DialogHostのDialogContentプロパティにダイアログのViewを設定します。
ダイアログのDataContextには対応するダイアログVMを設定します。

```xml:MainWindow.xaml(部分)
<material:DialogHost.DialogContent>
    <v:DialogWindow DataContext="{Binding DialogVM}" />
</material:DialogHost.DialogContent>
```

DialogHostのIsOpenプロパティの変更でダイアログの表示／非表示を切り替えます。
そしてそのIsOpenプロパティをダイアログVMのIsOpen(ReactiveProperty)にBindします。

```xml:MainWindow.xaml(部分)
<material:DialogHost IsOpen="{Binding DialogVM.IsOpen.Value}">
```

MainWindowVMからダイアログを開始する際は、そのBindされたIsOpenにtrueを設定します。
そしてすぐにIsOpenの変更（＝ダイアログの終了）を待機します。

```csharp:MainWindowViewModel.cs(部分)
DialogVM.IsOpen.Value = true;
await DialogVM.IsOpen;
```

ダイアログを閉じる際は
ダイアログVM内でIsOpenにfalseを設定します。
ダイアログの結果のわたし方は色々ありますが、今回はダイアログVM内のReactivePropertyとして表現しました。

```csharp:DialogWindowViewModel.cs(部分)
IsOK.Value = true;
IsOpen.Value = false;
```

ダイアログが閉じられたあとはMainWindowVMでダイアログVMのダイアログ結果を参照します。

```csharp:MainWindowViewModel.cs(部分)
DialogResultText.Value = DialogVM.IsOK.Value == true ? "おーけー" : "きゃんせる";
```

# デモプログラム実行結果
今回のデモプログラムの実行結果です。
中央のボタンを押すと、ダイアログが表示されます。
![スクリーンショット 2018-08-26 22.30.20.png](https://qiita-image-store.s3.amazonaws.com/0/120077/fd6c46ad-11c5-6860-932e-7a4bb91fc40a.png)
ダイアログ内にはちょっと怖めの確認文とOKとCancelの2つのボタンがあります。
どちらを押してもダイアログは閉じられます。
![スクリーンショット 2018-08-26 22.30.24.png](https://qiita-image-store.s3.amazonaws.com/0/120077/71794d39-ea8e-09bf-0d0d-9a9efe369b0b.png)
OKとCancelのどちらを押したかでMainWindowの文字が変わります。
![スクリーンショット 2018-08-26 22.30.29.png](https://qiita-image-store.s3.amazonaws.com/0/120077/18c4c1d6-027e-7bc2-3ab0-fea7fa15cb1a.png)

# 全体コード
デモプログラムの全体コードです。
## View

```MainWindow.xaml
<Window
    x:Class="ResponseDialogTest.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:material="http://materialdesigninxaml.net/winfx/xaml/themes"
    xmlns:v="clr-namespace:ResponseDialogTest.Views"
    xmlns:vm="clr-namespace:ResponseDialogTest.ViewModels"
    Width="400"
    Height="350">
    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>

    <!--  DialogHostのIsOpenプロパティをDialogVMのIsOpenプロパティにBindしている  -->
    <material:DialogHost IsOpen="{Binding DialogVM.IsOpen.Value}">
        <material:DialogHost.DialogContent>
            <v:DialogWindow DataContext="{Binding DialogVM}" />
        </material:DialogHost.DialogContent>

        <StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock FontSize="18" Text="ダイアログ結果:" />
                <TextBlock FontSize="18" Text="{Binding DialogResultText.Value}" />
            </StackPanel>
            <Button Command="{Binding StartDialogCommand}" Content="ダイアログ開始" />
        </StackPanel>
    </material:DialogHost>
</Window>
```

```DialogWindow.xaml
<UserControl
    x:Class="ResponseDialogTest.Views.DialogWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <UserControl.Resources>
        <Style BasedOn="{StaticResource MaterialDesignFlatButton}" TargetType="{x:Type Button}">
            <Setter Property="Margin" Value="10" />
            <Setter Property="Width" Value="150" />
        </Style>
    </UserControl.Resources>
    <StackPanel>
        <TextBlock
            Margin="15"
            HorizontalAlignment="Center"
            FontSize="24"
            Text="ほんとうに実行しますか？" />
        <StackPanel Orientation="Horizontal">
            <Button Command="{Binding OkCommand}" Content="OK" />
            <Button Command="{Binding CancelCommand}" Content="Cancel" />
        </StackPanel>
    </StackPanel>
</UserControl>
```

## ViewModel
```MainWindowViewModel.cs
public class MainWindowViewModel : ViewModel
{
    /// <summary>
    /// ダイアログ処理実行コマンド
    /// </summary>
    public AsyncReactiveCommand StartDialogCommand { get; } = new AsyncReactiveCommand();

    /// <summary>
    /// ダイアログ内容ViewModel
    /// </summary>
    public DialogWindowViewModel DialogVM { get; } = new DialogWindowViewModel();

    /// <summary>
    /// ダイアログ結果文字列
    /// </summary>
    public ReactiveProperty<string> DialogResultText { get; } = new ReactiveProperty<string>();

    public MainWindowViewModel()
    {
        StartDialogCommand.Subscribe(StartDialog);
    }

    private async Task StartDialog()
    {
        //ダイアログ内容VMのOpenプロパティを変更することで、ダイアログのOpen・Closeを制御する
        DialogVM.IsOpen.Value = true;

        //ダイアログのOpenプロパティが変更されるのを待つ
        await DialogVM.IsOpen;
            
        //ダイアログ結果を文字列に加工して表示
        DialogResultText.Value = DialogVM.IsOK.Value == true ? "おーけー" : "きゃんせる";
    }
}
```

```csharp:DialogWindowViewModel.cs
public class DialogWindowViewModel
{
    /// <summary>
    /// ダイアログのOpen状態
    /// </summary>
    public ReactiveProperty<bool> IsOpen { get; } = new ReactiveProperty<bool>(initialValue: false);

    /// <summary>
    /// ダイアログ結果（初期状態はNull）
    /// </summary>
    public ReactiveProperty<bool?> IsOK { get; } = new ReactiveProperty<bool?>(initialValue: null);

    public ReactiveCommand OkCommand { get; } = new ReactiveCommand();
    public ReactiveCommand CancelCommand { get; } = new ReactiveCommand();

    public DialogWindowViewModel()
    {
        //ダイアログが開いたときにダイアログ結果をNullに設定
        IsOpen
            .Where(x => x)
            .Subscribe(_ =>
                IsOK.Value = null);

        OkCommand.Subscribe(() =>
            {
                IsOK.Value = true;
                IsOpen.Value = false;
            });

        CancelCommand.Subscribe(() =>
            {
                IsOK.Value = false;
                IsOpen.Value = false;
            });
    }
}
```


# 環境
VisualStudio2017
.NET Framework 4.7.1
C#7.1
ReactiveProperty 5.2.0
MaterialDesignThemes 2.4.1.1101