WPFでいけてるダイアログを出す方法

#はじめに
WPFでダイアログメッセージをちょっとカッコ良く出したい！
そう思ったことはありませんか。
MahAppsとMaterial Design In XAML ToolkitはWPFで簡単に見た目をカッコ良くするライブラリです。
上記2つの基本的な説明は
[Material Design In XAML ToolkitでWPFアプリにモダンなUIを！](
http://qiita.com/smbkrysk14/items/3423c3202974ef21d1c0)
[Material Design In XAML Toolkitでお手軽にWPFアプリを美しく](http://neue.cc/2015/09/10_515.html)
等をご参考にしてください。

この記事では

- MahAppsダイアログ
- Materialダイアログ
- Materialスナックバー(番外編)

の3つについてコードビハインドとViewModelからの使用方法を説明します。

#MahAppsダイアログ
最初にWindowの中央を帯状に覆うMahAppsのダイアログです。
前提としてMahAppsを使うためにMainWindowはWindowではなくMetroWindowから継承します。

##コードビハインドから
コードビハインドから出す方法は簡単で、MetroWindowの```ShowMessageAsync()```を呼び出すだけです。

```xml:MainWindow.xaml(部分）
<Button Content="MahApps Dialog from Code Behind"
        Click="ButtonMahappsDialog_Click"/>
```

```CSharp:MainWindow.xaml.cs(部分）
private async void ButtonMahappsDialog_Click(object sender, RoutedEventArgs e)
{
    await this.ShowMessageAsync("Dialog from CodeBehind", $"Now = {DateTime.Now}");
}
```
実行結果
![スクリーンショット 2017-01-07 22.48.55.png](https://qiita-image-store.s3.amazonaws.com/0/120077/d5db0da8-00da-f3ec-f72d-85a98121bbcc.png)

##ViewModelから
ViewModelからの場合はIDialogCoordinatorを使って呼び出します。
まずViewのDialogCoordinatorをViewModelのコンストラクタ経由かプロパティ経由で渡します。
今回はコードビハインドをなるべく書かないため、プロパティ経由で渡します。
MahAppsライブラリのサンプルはコンストラクタ経由で書かれています。

```xml:MainWindow.xaml(部分）
<Controls:MetroWindow
～省略～
    Dialog:DialogParticipation.Register="{Binding}">
<Controls:MetroWindow.DataContext>
    <vm:MainWindowViewModel>
        <vm:MainWindowViewModel.MahAppsDialogCoordinator>
            <Dialog:DialogCoordinator/>
        </vm:MainWindowViewModel.MahAppsDialogCoordinator>
    </vm:MainWindowViewModel>
</Controls:MetroWindow.DataContext>
～省略～
            <Button Content="Mahapps Dialog from ViewModel"
                    Command="{Binding ShowDialogMahappsCommand}"
```

```csharp:MainWindowViewModel.cs(部分）
public IDialogCoordinator MahAppsDialogCoordinator { get; set; }
～省略～
ShowDialogMahappsCommand.Subscribe(async _ =>
    await this.MahAppsDialogCoordinator.ShowMessageAsync(
        this, "Dialog from ViewModel", $"Now = {DateTime.Now}"));
```

実行結果はコードビハインド版とほぼ同じなので省略します。

#Materialダイアログ
次は中央に浮き上がって見えるMaterialダイアログです。
ポイントはダイアログを出す部分を```<material:DialogHost>```の直下に置くことです。
またMahAppsと違ってデフォルトのダイアログウインドウとかはありませんから、自前でクラスを作る必要があります。

##ダイアログウインドウ
コードビハインド版・ViewModel版共通のダイアログウインドウのViewです。
注意点として、ダイアログ***ウインドウ***と書いてありますが、ダイアログホストに渡すViewはUserControlから継承する必要があります。

```xml:MaterialDialog.xaml
<UserControl x:Class="TestMaterial.Views.MaterialDialog"
             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">
    <StackPanel Margin="16">
        <Label Content="{Binding Title}" x:Name="labelTitle" FontSize="24"/>
        <Label Content="{Binding Message}" x:Name="labelMessage"/>
        <Button Content="OK"
                Style="{DynamicResource MaterialDesignFlatButton}"
                Command="{x:Static material:DialogHost.CloseDialogCommand}"/>
    </StackPanel>
</UserControl>
```

##コードビハインドから
自前のダイアログウインドウのコンストラクタで表示する内容を渡します。
そしてそのダイアログウインドウをMainWindowのDialogHostで表示します。

```xml:MainWindow.xaml(部分）
<material:DialogHost x:Name="dialogHost">
～省略～
        <Button Click="ButtonMaterialDialog_Click" Content="Material Dialog from Code Behind" />
～省略～
</material:DialogHost>
```

```CSharp:MainWindow.xaml.cs(部分）
private async void ButtonMaterialDialog_Click(object sender, RoutedEventArgs e)
{
    var dialogView = new MaterialDialog("Dialog from Code Behind", $"Now = {DateTime.Now}");
    var result = await dialogHost.ShowDialog(dialogView);
}
```

```csharp:MaterialDialog.xaml.cs（部分）
public MaterialDialog(string title, string message) : this()
{
    labelTitle.Content = title;
    labelMessage.Content = message;
}
```

実行結果です。
![スクリーンショット 2017-01-08 00.31.24.png](https://qiita-image-store.s3.amazonaws.com/0/120077/cbcfbbe0-aa21-16e4-1e97-b2f4526f2385.png)

##ViewModelから
ViewModelの場合は表示状態と表示内容をプロパティでバインドします。

```xml:MainWindow.xaml(部分）
<Controls:MetroWindow.Resources>
～省略～
    <DataTemplate DataType="{x:Type vm:MaterialDialogViewModel}">
        <v:MaterialDialog/>
    </DataTemplate>
</Controls:MetroWindow.Resources>
<material:DialogHost DialogContent="{Binding DialogVM.Value}" IsOpen="{Binding IsDialogOpen.Value}">
～省略～
            <Button Command="{Binding ShowDialogMaterialCommand}" Content="Material Dialog from ViewModel" />
</material:DialogHost>
```

こちらは自前のダイアログウインドウのコンストラクタ経由ではなく、ダイアログウインドウのViewModelを作って渡しています。
※追記1　ViewModelでViewを生成するのを止めて、View側のDataTemplateでダイアログのViewとViewModelを結びつける形に変更
※追記2　ViewModelでDialogHostを呼ぶのは止めて、表示状態プロパティの変化でダイアログを表示する形に変更

```csharp:MainWindowViewModel.cs(部分）
ShowDialogMaterialCommand.Subscribe(_ =>
{
    this.DialogVM.Value = new MaterialDialogViewModel(
        "Dialog from ViewModel",
        $"Now = { DateTime.Now}");
    this.IsDialogOpen.Value = true;
});
```

```csharp:MaterialDialogViewModel.cs
using Livet;
namespace TestMaterial.ViewModels
{
    public class MaterialDialogViewModel : ViewModel
    {
        public string Title { get; set; }
        public string Message { get; set; }

        public MaterialDialogViewModel(string title, string message)
        {
            this.Title = title;
            this.Message = message;
        }
    }
}
```

実行結果はコードビハインドとほぼ同じなので省略します。

#Materialスナックバー(番外編)
ダイアログとはちょっと違いますが、同じくメッセージを表示するためのUIとしてスナックバーがあります。
ダイアログとは違い、スレッドをブロックせず、しばらく立つと自動で消えます。
一番良く見るのはスマートフォンの通知メッセージですね。

##コードビハインドから
スナックバーを表示したい場所に```<material:Snackbar>```を置きます。
デフォルトではメッセージが無いので最初は表示されません。

```xml:MainWindow.xaml(部分）
<Button Content="SnackBar from Code Behind"
        Click="ButtonSnackBar_Click"/>
<material:Snackbar x:Name="Snackbar1"
                    MessageQueue="{material:MessageQueue}"/>
```

SnackbarのMessageQueueにメッセージを送ります。
Queueと書いてあることから分かるように、
複数のメッセージを送ると順番に表示されます。

```CSharp:MainWindow.xaml.cs(部分）
private void ButtonSnackBar_Click(object sender, RoutedEventArgs e)
{
    Snackbar1.MessageQueue
        .Enqueue($"SnackBar from Code Behind Now = {DateTime.Now}");
}
```

実行結果です。
![スクリーンショット 2017-01-08 00.49.59.png](https://qiita-image-store.s3.amazonaws.com/0/120077/b552d8de-6487-4778-bc5d-a01d1d5bb6ba.png)

##ViewModelから
ViewModel側にSnackbarMessageQueueを保持してViewはそれにバインディングします。

```xml:MainWindow.xaml(部分）
<Button Content="SnackBar from ViewModel"
        Command="{Binding ShowSnackBarCommand}"/>
<material:Snackbar MessageQueue="{Binding SnackBarMessageQueue}"
                    Background="{StaticResource AccentColorBrush}"/>
```

```csharp:MainWindowViewModel.cs（部分）
public SnackbarMessageQueue SnackBarMessageQueue { get; private set; }
～省略～
ShowSnackBarCommand.Subscribe(_ =>
    this.SnackBarMessageQueue.Enqueue(
        $"SnackBar from ViewModel Now = { DateTime.Now}"));
```

実行結果です。
![スクリーンショット 2017-01-08 00.50.05.png](https://qiita-image-store.s3.amazonaws.com/0/120077/7828521b-c477-6fb4-c3db-004290124b1a.png)

#全体コード
一部抜粋して表示していたコード全体です。

```xml:MainWindow.xaml（全体）
<Controls:MetroWindow
    x:Class="TestMaterial.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
    xmlns:Dialog="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
    xmlns:material="http://materialdesigninxaml.net/winfx/xaml/themes"
    xmlns:v="clr-namespace:TestMaterial.Views"
    xmlns:vm="clr-namespace:TestMaterial.ViewModels"
    Width="525"
    Height="400"
    Dialog:DialogParticipation.Register="{Binding}">
    <Controls:MetroWindow.DataContext>
        <vm:MainWindowViewModel>
            <vm:MainWindowViewModel.MahAppsDialogCoordinator>
                <Dialog:DialogCoordinator />
            </vm:MainWindowViewModel.MahAppsDialogCoordinator>
        </vm:MainWindowViewModel>
    </Controls:MetroWindow.DataContext>

    <Controls:MetroWindow.Resources>
        <Style BasedOn="{StaticResource MaterialDesignRaisedAccentButton}" TargetType="{x:Type Button}">
            <Setter Property="Margin" Value="10" />
        </Style>
        <DataTemplate DataType="{x:Type vm:MaterialDialogViewModel}">
            <v:MaterialDialog />
        </DataTemplate>
    </Controls:MetroWindow.Resources>

    <material:DialogHost DialogContent="{Binding DialogVM.Value}" IsOpen="{Binding IsDialogOpen.Value}">
        <material:DialogHost x:Name="dialogHost">
            <StackPanel>
                <!--  MahAppsダイアログ  -->
                <Button Click="ButtonMahappsDialog_Click" Content="MahApps Dialog from Code Behind" />
                <Button Command="{Binding ShowDialogMahappsCommand}" Content="Mahapps Dialog from ViewModel" />
                <!--  Materialダイアログ  -->
                <Button Click="ButtonMaterialDialog_Click" Content="Material Dialog from Code Behind" />
                <Button Command="{Binding ShowDialogMaterialCommand}" Content="Material Dialog from ViewModel" />
                <!--  Materialスナックバー  -->
                <Button Click="ButtonSnackBar_Click" Content="SnackBar from Code Behind" />
                <material:Snackbar x:Name="Snackbar1" MessageQueue="{material:MessageQueue}" />
                <Button Command="{Binding ShowSnackBarCommand}" Content="SnackBar from ViewModel" />
                <material:Snackbar Background="{StaticResource AccentColorBrush}" MessageQueue="{Binding SnackBarMessageQueue}" />
            </StackPanel>
        </material:DialogHost>
    </material:DialogHost>
</Controls:MetroWindow>
```

```csharp:MainWindowViewModel.cs(全体）
using System;
using System.Windows;
using MahApps.Metro.Controls.Dialogs;
using MaterialDesignThemes.Wpf;

namespace TestMaterial.Views
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void ButtonMahappsDialog_Click(object sender, RoutedEventArgs e)
        {
            await this.ShowMessageAsync("Dialog from CodeBehind", $"Now = {DateTime.Now}");
        }

        private async void ButtonMaterialDialog_Click(object sender, RoutedEventArgs e)
        {
            var dialogView = new MaterialDialog("Dialog from Code Behind", $"Now = {DateTime.Now}");
            var result = await dialogHost.ShowDialog(dialogView);
        }

        private void ButtonSnackBar_Click(object sender, RoutedEventArgs e)
        {
            Snackbar1.MessageQueue
                .Enqueue($"SnackBar from Code Behind Now = {DateTime.Now}");
        }
    }
}
```

#まとめ
MahAppsとMaterial Design In XAML Toolkitを使って3つのメッセージ表示について説明しました。
これでもうWPFデフォルトのださいメッセージダイアログとはおさらば出来ますね！

#環境
VisualStudio2015
.NET Framework 4.6
C#6
ReactiveProperty