結局最後は「MVVM + Messenger + Behavior パターン」に行き着いた

はじめに

前回、Messenger パターンという、ViewModel から View に要求を送る方法を試しました。

ただ、「View のコードビハインドにメッセージを処理するコードなんて書きたくない!」っていう人は結構いるみたいです。私もできることならコードビハインド書きたくないですね。

そんな人向けに、「Blend SDK に含まれている System.Windows.Interactivity.dll を使って、メッセージに対する処理を XAML で書いてしまおう」という、 Messenger を発展させたパターンがありました。その名も「Messenger + Behavior パターン」。

Messenger + Behavior パターンの鍵

System.Windows.Interactive.dll で提供されている Interaction クラスが重要。こいつは DependencyObject の派生クラスにトリガーやビヘイビアを設定するための添付プロパティを提供しています。

簡単に言うと、Interaction クラスを使って、メッセージに対する処理(振る舞い)を、View に添付プロパティとして設定しちゃおう、ってことです。

Messenger + Behavior パターンを実装してみます

今回も MVVM Light Toolkit を使います。ダウンロードはこちら。

Blend SDK はここからダウンロードできます。Expression Blend 買わなくてもできるよ!

MVVM Light Toolkit には System.Windows.Interactive.dll が同梱されているので、それを使ってもいいです。

まず Messenger に反応するトリガーを実装します

ViewModel が送信した DialogMessage に反応して、何かアクションを実行するトリガーです。

using System.Windows;
using System.Windows.Interactivity;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLightSample
{
    // Messenger が DialogMessage を受信したときに反応するトリガー。
    public class DialogMessageTrigger : TriggerBase<DependencyObject>
    {
        protected override void OnAttached()
        {
            base.OnAttached();

            // DialogMessage を受信したらアクションを実行
            // AssociatedObject は添付プロパティでこのトリガーを設定したオブジェクト。
            Messenger.Default.Register<DialogMessage>(
                AssociatedObject,
                msg => InvokeActions(msg));
        }

        protected override void OnDetaching()
        {
            Messenger.Default.Unregister<DialogMessage>(AssociatedObject);

            base.OnDetaching();
        }
    }
}
MessageBox を表示するアクションを実装します

DialogMessage を受け取って、MessageBox を表示するアクションです。先ほど作成した DialogMessageTrigger と組み合わせて使います。

using System.Windows;
using System.Windows.Interactivity;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLightSample
{
    // MessageBox を表示するアクション
    public class ShowMessageBoxAction : TriggerAction<DependencyObject>
    {
        protected override void Invoke(object parameter)
        {
            // DialogMessage を渡されたら
            // MessageBox を表示する
            var msg = parameter as DialogMessage;
            if (msg != null)
            {
                var result = MessageBox.Show(
                    msg.Content,
                    msg.Caption,
                    msg.Button,
                    msg.Icon,
                    msg.DefaultResult,
                    msg.Options);
                msg.Callback(result);
            }
        }
    }
}
View を実装します

作成したトリガーとアクションを、Interaction クラスを使って View に設定します。これで ViewModel から DialogMessage を送信されると、この View が MessageBox を表示するようになりました。

<Window x:Class="MvvmLightSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:l="clr-namespace:MvvmLightSample"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{StaticResource MainViewModel}">
  
    <!--ViewModel が送った DialogMessage を受信したら MessageBox を表示-->
    <i:Interaction.Triggers>
        <l:DialogMessageTrigger>
            <l:ShowMessageBoxAction/>
        </l:DialogMessageTrigger>
    </i:Interaction.Triggers>
    
    <StackPanel>
        <Label Content="名前"/>
        <TextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"/>
        <Button Content="あいさつする!"
                Command="{Binding Path=GreetCommand}"/>
    </StackPanel>
</Window>

あと、コードビハインドには何も書いていません。

ViewModel は前回のものをそのまま流用

MVVM Light Toolkit が提供している Messenger パターンの実装を利用して、ViewModel から View にダイアログの表示をリクエストしています。

using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLightSample
{
    public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
            : base(Messenger.Default)
        {
        }

        private string _name;
        
        // 名前。
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    RaisePropertyChanged("Name");
                }
            }
        }

        private ICommand _greetCommand;
        
        // 挨拶コマンド。
        public ICommand GreetCommand
        {
            get
            {
                return _greetCommand ??
                    (_greetCommand = new RelayCommand(GreetCommandExecute, CanGreetCommandExecute));
            }
        }

        // 挨拶コマンドの実行。
        private void GreetCommandExecute()
        {
            string greet = string.Format("Hello,{0}!", Name);
            MessengerInstance.Send(new DialogMessage(this, greet, result =>
            {
                // MessageBos を表示した後の処理をここに書く
                Name = string.Empty;
            }));
        }

        // 挨拶コマンドを実行できるか判定。
        private bool CanGreetCommandExecute()
        {
            return !string.IsNullOrEmpty(Name);
        }
    }
}
App も前回のをそのまま流用

ViewModel のインスタンスを App のリソースとして持たせています。

<Application x:Class="MvvmLightSample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MvvmLightSample"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <local:MainViewModel x:Key="MainViewModel"/>
    </Application.Resources>
</Application>
実行!

f:id:griefworker:20110221165836p:image
ボタンをクリックすると
f:id:griefworker:20110221165848p:image
ちゃんと MessageBox が表示されました。

まとめ

Blend SDK の System.Windiws.Interactivity.dll で提供されている機能を使えば、コードビハインドに何も書かずに、XAML だけでメッセージに対する処理が書けます。

ダイアログ表示やフォーカス移動や画面遷移などは頻繁に実装する処理なので、アクションとして実装しておけば、使い回すことが可能になりますね。素敵。

Messenger + Behavior パターンについては、えムナウさんや @ugaya40 さんのブログが詳しいです。MVVM やるならオススメ。