[WPF] MessageBox をメソッドチェーンする


@Temarin_PITA さんの [何でもメソッドチェーンしたい](http://qiita.com/Temarin_PITA/items/001f71d70474063caab5) を読んだらチェーンしたくなった。

# メッセージボックスを表示する

普段、メッセージボックスを表示してユーザーが押下したボタンに応じて処理をするとき

```csharp:いつもの
var result = MessageBox.Show("メッセージ", "タイトル", MessageBoxButton.OKCancel, MessageBoxImage.Question);

if (result == MessageBoxResult.OK)
{
    // OK のとき
    Console.WriteLine("ok");
}
else
{
    // それ以外
    Console.WriteLine("cancel");
}
```

みたいな感じで書きます。 if 文で結果に応じて処理を分岐させるあれです。

```csharp:あれ
if (result == MessageBoxResult.OK)
```

今回は `MessageBox.Show()` した結果を if 文の条件分岐を使わないでチェーンして処理できるようにします。

```csharp:チェーンしたイメージ
msgBox("メッセージ", "タイトル")
    .OK(() => Console.WriteLine("ok"))
    .Cancel(() => Console.WriteLine("cancel"))
    .Show();
```

# コード

```csharp:ChainableMessageBox.cs
using System;
using System.Collections.Generic;
using System.Windows;

namespace WpfApplication1
{
    /// <summary>
    /// チェーン可能なメッセージ ボックスを表示します。
    /// </summary>
    public sealed class ChainableMessageBox
    {
        /// <summary>
        /// メッセージ ボックスで押下されたボタンに応じて実行する処理を管理します。
        /// </summary>
        private Dictionary<MessageBoxResult, Action> _acts = new Dictionary<MessageBoxResult, Action>();

        /// <summary>
        /// メッセージ ボックスを表示する関数を取得します。
        /// </summary>
        private Func<MessageBoxResult> ShowMessageBox { get; }

        private ChainableMessageBox(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
        {
            // メッセージ ボックスを表示する関数を設定
            ShowMessageBox = () => MessageBox.Show(messageBoxText, caption, button, icon);
        }

        /// <summary>
        /// メッセージ ボックスで押下されたボタンに応じて実行する処理を設定します。
        /// </summary>
        /// <param name="result">メッセージ ボックスで押下されたボタン</param>
        /// <param name="act">実行する処理</param>
        private void SetAct(MessageBoxResult result, Action act)
        {
            if (_acts.ContainsKey(result))
                _acts[result] += act;
            else
                _acts.Add(result, act);
        }

        /// <summary>
        /// メッセージ ボックスで押下されたボタンが OK のときに実行する処理を設定します。
        /// </summary>
        /// <param name="act">実行する処理</param>
        /// <returns>ChainableMessageBox</returns>
        public ChainableMessageBox OK(Action act)
        {
            SetAct(MessageBoxResult.OK, act);
            return this;
        }

        /// <summary>
        /// メッセージ ボックスで押下されたボタンが Yes のときに実行する処理を設定します。
        /// </summary>
        /// <param name="act">実行する処理</param>
        /// <returns>ChainableMessageBox</returns>
        public ChainableMessageBox Yes(Action act)
        {
            SetAct(MessageBoxResult.Yes, act);
            return this;
        }

        /// <summary>
        /// メッセージ ボックスで押下されたボタンが No のときに実行する処理を設定します。
        /// </summary>
        /// <param name="act">実行する処理</param>
        /// <returns>ChainableMessageBox</returns>
        public ChainableMessageBox No(Action act)
        {
            SetAct(MessageBoxResult.No, act);
            return this;
        }

        /// <summary>
        /// メッセージ ボックスで押下されたボタンが Cancel のときに実行する処理を設定します。
        /// </summary>
        /// <param name="act">実行する処理</param>
        /// <returns>ChainableMessageBox</returns>
        public ChainableMessageBox Cancel(Action act)
        {
            SetAct(MessageBoxResult.Cancel, act);
            return this;
        }

        /// <summary>
        /// メッセージ ボックスを表示します。
        /// </summary>
        public void Show()
        {
            // メッセージボックスを表示して結果を受取る
            var result = ShowMessageBox();

            // 存在しないかあっても null の場合は何もしない
            if (!_acts.ContainsKey(result) || _acts[result] == null)
                return;

            // 実行
            _acts[result]();
        }

        /// <summary>
        /// メッセージ ボックスを生成します。
        /// </summary>
        /// <param name="messageBoxText">表示するテキスト</param>
        /// <param name="caption">表示するタイトル バー キャプション</param>
        /// <param name="button">表示するボタン</param>
        /// <param name="icon">表示するアイコン</param>
        /// <returns>ChainableMessageBox</returns>
        public static ChainableMessageBox Create(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
        {
            return new ChainableMessageBox(messageBoxText, caption, button, icon);
        }

        /// <summary>
        /// メッセージ ボックスを生成します。
        /// </summary>
        /// <param name="button">表示するボタン</param>
        /// <param name="icon">表示するアイコン</param>
        /// <returns>表示するテキストとタイトル バー キャプションを引数として ChainableMessageBox を返す System.Func</returns>
        public static Func<string, string, ChainableMessageBox> Create(MessageBoxButton button, MessageBoxImage icon)
        {
            return (msg, cap) => new ChainableMessageBox(msg, cap, button, icon);
        }
    }
}
```

`MessageBox` と似た感じで扱えるように実装しました。なので `sealed class` にして派生クラスの作成ができないようにしています。あと、コンストラクタを`private` にして、直接 `new` してインスタンス化できないようにしています。

インスタンスを取得するときは `Create()` メソッドを呼びます。 今回は `Create()` を二つ用意しています。

1. 表示するメッセージボックスの内容をあらかじめ決めてしまうパターン
2. 先に表示するボタンとアイコンを設定しておいて、あとからテキストとタイトルを指定できるように部分適用しているパターン

# 使用例
`Create()` したら `OK()` や `Cancel()` などでボタンに処理を割り当てて、最後に `Show()` メソッドを呼ぶとメッセージボックスが表示されます。

まずは普通に

```csharp:ex1
// 確認
var confirm = ChainableMessageBox
    .Create("確認です。", "確認", MessageBoxButton.OKCancel, MessageBoxImage.Question);

confirm
    .OK(() => Console.WriteLine("conf:ok"))
    .Cancel(() => Console.WriteLine("conf:cancel"))
    .Show();
 
// YesNoCancel
ChainableMessageBox
    .Create("メッセージ", "タイトル", MessageBoxButton.YesNoCancel, MessageBoxImage.Question)
    .Yes(() => Console.WriteLine("yes"))
    .No(() => Console.WriteLine("no"))
    .Cancel(() => Console.WriteLine("cancel"))
    .Show();
```

部分適用の場合

```csharp:ex2
// 確認
var confirm = ChainableMessageBox.Create(MessageBoxButton.OKCancel, MessageBoxImage.Question);

confirm("確認です。", "確認")
    .OK(() => Console.WriteLine("conf:ok"))
    .Cancel(() => Console.WriteLine("conf:cancel"))
    .Show();
    
confirm("再確認です。", "再確認")
    .OK(() => Console.WriteLine("re;conf:ok"))
    .Cancel(() => Console.WriteLine("re;conf:cancel"))
    .Show();
     
// YesNoCancel
ChainableMessageBox
.Create(MessageBoxButton.YesNoCancel, MessageBoxImage.Question)("メッセージ", "タイトル")
    .Yes(() => Console.WriteLine("yes"))
    .No(() => Console.WriteLine("no"))
    .Cancel(() => Console.WriteLine("cancel"))
    .Show();
```

あと、async/await したり

```csharp:ex3
 // エラー
 var error = ChainableMessageBox.Create(MessageBoxButton.OK, MessageBoxImage.Error);

 error("エラーです。", "エラー")
     .OK(async () =>
     {
         checkBox.IsChecked = false;
         checkBox.IsEnabled = false;

        // なにか重たい処理...
         await Task.Delay(TimeSpan.FromSeconds(2));
         
         checkBox.IsChecked = true;
         checkBox.IsEnabled = true;
     })
     .Show();
```

# 問題点

問題点は `MessageBoxButton` で選択されている内容に関わらず、すべてのボタンに処理を割り当てられちゃうとこでしょうか。

```csharp:問題点
var msgBox = ChainableMessageBox.Create(MessageBoxButton.OK, MessageBoxImage.Exclamation);
msgBox("問題点です。", "問題点")
    .OK(() => Console.WriteLine("ok"))
    .Yes(() => Console.WriteLine("yes"))
    .No(() => Console.WriteLine("no"))
    .Cancel(() => Console.WriteLine("cancel"))
    .Show();
```

気になる場合は `MessageBoxButton` のタイプごとに作成が必要ですね。