概要
MVVMでつまずくポイントとしてよく挙げられるのが、ダイアログ表示です。
単純にダイアログを表示して、OKを押されたら閉じる、という内容を以前に書きました。
WPFでいけてるダイアログを出す方法 - Qiita
今回はダイアログが閉じられるのを待って結果を受け取る方法を
Material Design In XAML ToolkitとReactivePropertyを使用して説明したいと思います。
なお今回の内容は現時点(2018/08/26)で最新のReactiveProperty 5.2.0が必要です。
説明
ダイアログはMaterial Design In XAML ToolkitのDialogHostを使用して表示します。
DialogHostのDialogContentプロパティにダイアログのViewを設定します。
ダイアログのDataContextには対応するダイアログVMを設定します。
<material:DialogHost.DialogContent>
<v:DialogWindow DataContext="{Binding DialogVM}" />
</material:DialogHost.DialogContent>
DialogHostのIsOpenプロパティの変更でダイアログの表示/非表示を切り替えます。
そしてそのIsOpenプロパティをダイアログVMのIsOpen(ReactiveProperty)にBindします。
<material:DialogHost IsOpen="{Binding DialogVM.IsOpen.Value}">
MainWindowVMからダイアログを開始する際は、そのBindされたIsOpenにtrueを設定します。
そしてすぐにIsOpenの変更(=ダイアログの終了)を待機します。
DialogVM.IsOpen.Value = true;
await DialogVM.IsOpen;
ダイアログを閉じる際は
ダイアログVM内でIsOpenにfalseを設定します。
ダイアログの結果のわたし方は色々ありますが、今回はダイアログVM内のReactivePropertyとして表現しました。
IsOK.Value = true;
IsOpen.Value = false;
ダイアログが閉じられたあとはMainWindowVMでダイアログVMのダイアログ結果を参照します。
DialogResultText.Value = DialogVM.IsOK.Value == true ? "おーけー" : "きゃんせる";
デモプログラム実行結果
今回のデモプログラムの実行結果です。
中央のボタンを押すと、ダイアログが表示されます。
ダイアログ内にはちょっと怖めの確認文とOKとCancelの2つのボタンがあります。
どちらを押してもダイアログは閉じられます。
OKとCancelのどちらを押したかでMainWindowの文字が変わります。
全体コード
デモプログラムの全体コードです。
View
<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>
<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
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 ? "おーけー" : "きゃんせる";
}
}
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