以下の内容はhttps://takamints.hatenablog.jp/entry/mvvm-messageboxより取得しました。


MVVM的に真っ当にMessageBoxを表示する

f:id:takamints:20161023233634p:plain
photo credit: waterj2 DSCF0629 via photopin (license)

MVVM的に正しくモーダルなメッセージボックスを表示するサンプルコードを示します。

WPFでのMVVMパターンとしては、ビュー以外からメッセージボックスを直接表示するのは良くないらしいです。

メッセージボックスで親ウィンドウを指定しないとモーダルにならないのですが「ビュー以外からメインウィンドウを逆方向へ参照するのは悪手」ということです。

じゃあ、どうすればよいかっていうと「メッセンジャーパターン」を使いましょうとのことでした。

posted with amazlet at 16.10.23
五十嵐 祐貴 生形 可奈子 大田 一希 古賀 慎一 酒井 達明 芝村 達郎 田淵 義人
日経BP
売り上げランキング: 41,307
posted with amazlet at 16.10.23

メッセンジャーパターン」て?

メッセンジャーパターンは「VMからViewを操作する方法」のことらしいです。 以下のようなカラクリだと理解しました。

  1. メッセンジャー」というオブジェクトからイベントを発行して、
  2. あらかじめビューに実装されたイベントトリガーが、このイベントを受けて、
  3. イベントトリガーが保有するトリガーアクションが処理をする(イベントの引数も渡される)。

上に書いた点を自分なりに整理して、ややこしいことを抜きで System.Windows.MessageBox と同じように使えるコードをご紹介します。 GitHub Gistにも置いていますのでご自由にご利用ください。

コードの概要

  1. メッセンジャーとイベントトリガー(これが本体)
  2. トリガーアクションの実装例
  3. メインウィンドウ(MainWindow.xaml)の記述例
  4. メッセージボックスを表示する

ここで、

  1. メッセンジャーは単なるクラス。
  2. イベントトリガーは、System.Windows.Interactibity.EventTriggerの派生クラス。
  3. トリガーアクションは、System.Windows.Interactibity.TriggerActionの派生クラス。

※ System.Windows.Interactibityは、プロジェクトから参照されていないかもしれません。 参照マネージャの左のツリーから[アセンブリ]/[拡張]を開いてチェックを付ければOKです。

1.メッセンジャーとイベントトリガー(これが本体)

内部クラスを含めて3つのクラスを定義しています。System.Windows.MessageBoxと同じように使えるようにしたら長くなりました。

  1. MessageBox - メッセンジャー。スタティックなShowメソッドでシングルトンのインスタンスからイベントを発行。
  2. MessageBox.Action トリガーアクションの抽象基底クラス。実際にメッセージを表示するクラスの基本クラスです。
  3. MessageBoxTrigger - イベントトリガークラス。MessageBoxからのイベントを受け取って、トリガーアクションを実行します。

MessageBoxTrigger.cs

using System;
using System.Linq;
using System.Windows;
using System.Windows.Interactivity;

namespace StudyDotNet.Triggers
{
    /// <summary>
    /// MVVM的メッセージボックスを表示するためのメッセンジャー。
    ///
    /// MessageBox.Showメソッドで、イベントトリガーを起動する。
    /// 
    /// 実際の表示は、イベントトリガーから実行される
    /// トリガーアクションに実装される。
    /// </summary>
    public class MessageBox
    {
        /// <summary>
        /// MessageBoxTriggerを起動するイベント。
        /// </summary>
        public event EventHandler<EventArgs> ShowMessageBox;

        /// <summary>
        /// MessageBoxTriggerを起動するイベントの名前。
        /// </summary>
        public static string EventName
        { get { return "ShowMessageBox"; } }

        /// <summary>
        /// このクラスはシングルトン。
        /// </summary>
        public static MessageBox Instance
        { get; private set; } = new MessageBox();
        private MessageBox() { }

        /// <summary>
        /// MessageBoxTriggerを起動するイベントの引数。
        /// トリガーアクションへ渡されて処理される。
        /// </summary>
        public class EventArgs : System.EventArgs
        {
            public string Text { get; set; }
            public string Title { get; set; }
            public MessageBoxButton Button { get; set; }
            public MessageBoxImage Icon { get; set; }

            /// <summary>
            /// メッセージボックスの結果を受け取るコールバック
            /// </summary>
            public Action<MessageBoxResult> NotifyResult
            { get; set; }
        }

        /// <summary>
        /// MVVM的メッセージボックスを表示。
        /// 実際にはイベントを発行してイベントトリガーを起動する。
        /// </summary>
        /// <param name="messageBoxText"></param>
        /// <param name="title"></param>
        /// <param name="button"></param>
        /// <param name="icon"></param>
        /// <returns></returns>
        public static MessageBoxResult Show(
            string messageBoxText,
            string title = null,
            MessageBoxButton button = MessageBoxButton.OK,
            MessageBoxImage icon = MessageBoxImage.Information)
        {
            //メッセージボックスの結果
            MessageBoxResult messageBoxResult = MessageBoxResult.Cancel;

            //イベントを発行する
            Instance.ShowMessageBox?.Invoke(
                Instance,
                new MessageBox.EventArgs()
                {
                    Text = messageBoxText,
                    Title = title,
                    Button = button,
                    Icon = icon,

                    //コールバックで結果を受け取る
                    NotifyResult = result =>
                    {
                        messageBoxResult = result;
                    }
                });

            //メッセージボックスの結果を返す
            return messageBoxResult;
        }

        /// <summary>
        /// メッセージを表示するトリガーアクション実装用の抽象基底クラス。
        /// 
        /// 派生クラスでShowMessageを実装する。 
        /// </summary>
        public abstract class Action : TriggerAction<DependencyObject>
        {
            /// <summary>
            /// アクションの実態
            /// </summary>
            /// <param name="parameter"></param>
            protected override void Invoke(object parameter)
            {
                //イベント引数の種別を検査
                var messageBoxArgs = parameter as MessageBox.EventArgs;
                if(messageBoxArgs == null)
                {
                    return;
                }

                //メッセージボックスの表示結果を取得
                MessageBoxResult result = ShowMessage(
                        messageBoxArgs.Text,
                        messageBoxArgs.Title,
                        messageBoxArgs.Button,
                        messageBoxArgs.Icon);

                //コールバックで結果を通知
                messageBoxArgs.NotifyResult?.Invoke(result);
            }

            /// <summary>
            /// メッセージボックスを表示する抽象メソッド。
            /// </summary>
            /// <param name="text"></param>
            /// <param name="title"></param>
            /// <param name="button"></param>
            /// <param name="icon"></param>
            /// <returns></returns>
            abstract protected MessageBoxResult
            ShowMessage(
                string text, string title,
                MessageBoxButton button,
                MessageBoxImage icon);
        }
    }

    /// <summary>
    /// MVVM的メッセージボックスを表示するためのイベントトリガー。
    ///
    /// MessageBox メッセンジャーの発行するイベントで起動される。
    /// </summary>
    public class MessageBoxTrigger : System.Windows.Interactivity.EventTrigger
    {
        public MessageBoxTrigger()
            : base(MessageBox.EventName)
        {
            SourceObject = MessageBox.Instance;
        }
    }
}

2.トリガーアクションの実装例

MesssageBoxのトリガーから実行されるトリガーアクションクラスの実装例です。

MessageBox.Actionから派生した、MessageBoxActionクラスを実装しています。 メッセージボックスを表示して、その結果を返すクラスです。

このクラスから使っている MessageBox クラスは、System.Windows のものということに注意してください。

MessageBoxAction.cs

using System.Windows;

namespace StudyDotNet.Triggers
{
    /// <summary>
    /// メッセージボックスを表示するトリガーアクション
    /// </summary>
    public class MessageBoxAction : MessageBox.Action
    {
        protected override MessageBoxResult ShowMessage(
            string text,
            string title,
            MessageBoxButton button,
            MessageBoxImage icon)
        {
            var owner = Application.Current.Windows
                .OfType<Window>().SingleOrDefault(
                    x => x.IsActive);
            if(owner == null)
            {
                owner = Window.GetWindow(AssociatedObject);
            }
            return System.Windows.MessageBox.Show(
                owner, text,
                (title != null ? title :
                    Application.Current.MainWindow.Title),
                button, icon);
        }
    }
}

追記(2017-03-29): メッセージボックスのオーナーウィンドウに、アクティブウィンドウを指定するようにしました。メッセージボックスが出ている場合はnullになりうるのでその場合は従来通りのウィンドウとしています。(c# - Refer to active Window in WPF? - Stack Overflow

3.メインウィンドウ(MainWindow.xaml)の記述例

上のメッセージトリガーとトリガーアクションクラスは以下のように、Xamlに Interaction.Trigger 要素を追加します。

XML名前空間xmlns:ixmlns:tr の宣言にも注意。iは、System.Windows.Interactibityを使用するためで、 trは、上記のトリガー/アクションを使用するためのものです。

<Window x:Class="StudyDotNet.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StudyDotNet"
        xmlns:vm="clr-namespace:StudyDotNet.ViewModels"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
        xmlns:tr="clr-namespace:StudyDotNet.Triggers"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>

    <!-- ここから -->
    <i:Interaction.Triggers>
        <tr:MessageBoxTrigger>
            <tr:MessageBoxAction />
        </tr:MessageBoxTrigger>
    </i:Interaction.Triggers>
    <!-- ここまで -->

    <Grid>
        <Button x:Name="button" Content="Button"
                Command="{Binding SampleCommand}"
                HorizontalAlignment="Left" Margin="50,50,0,0"
                VerticalAlignment="Top" Width="75"/>
    </Grid>
</Window>

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

使う側からは単純に、MessageBox.Showを呼び出すだけです。 System.Windows.MessageBoxとゴッチャにならないように注意は必要。

using System;
using System.Windows.Input;
using StudyDotNet.Triggers;

namespace StudyDotNet.Commands
{
    public class SampleCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        { return true; }

        public void Execute(object parameter)
        {
            MessageBox.Show("MVVMバンザイ!");
        }
    }
}

いくつになってもお勉強

これによって、より少ない変更で、従来のメッセージボックス使用部分をメッセンジャーパターンに移行できます。

ユニットテストは、固定の応答を返すスタブで対応。

また、別の表示方法に切り替えるのも容易です。

例えば独自のウィンドウで表示したり、 ポップアップせずメインウィンドウ内にメッセージを配置したり、 履歴を参照する機能を追加したりと、広がりがあります。

ということで、やはりビュー以外からは、表示内容やUIに直接の関わりを持たないほうが良いのでしょう。

いくつになってもお勉強です。

posted with amazlet at 16.10.23
日経BP社 (2016-02-04)
売り上げランキング: 8,426
posted with amazlet at 16.10.23
日経BP社 (2016-02-04)
売り上げランキング: 77,969



以上の内容はhttps://takamints.hatenablog.jp/entry/mvvm-messageboxより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14