いまさら聞けない「MVVM + Messenger パターン」超入門

はじめに

以前、MVVM パターンで ViewModel から View を操作する話題で祭になりました。

その後しばらく WPFSilverlight から離れていたんですが、その間に Messenger というパターンが有力になってきたみたいです。さっそく調査開始。

Messenger パターンって?

簡単に説明すると、View と ViewModel が共通の Messenger を参照し、ViewModel は Messenger を介して View に UI 操作をリクエストする、というパターン。図にするとこんな感じです。
f:id:griefworker:20110218115143j:image:w300

ViewModel から View を操作する方法でもっと詳しい情報を知りたければ、@ugaya40 さんのブログがオススメ。

Messenger パターンを試してみます

RelayCommand や ViewModelBase を自分で作るのは面倒なので、今回は MVVM Light Toolkit というフレームワークを使ってみます。

MVVM Light Toolkit は Messenger パターンの実装を提供していて、それでいてシンプルなフレームワークになっています。

MVVM Light Toolkit のダウンロードはこちら。

まず ViewModel の実装します

MVVM Light Toolkit は ViewModelBase クラスを提供しているので、ViewModel はこれを使って実装します。

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

namespace MvvmLightSample
{
    public class MainViewModel : ViewModelBase
    {
        // デフォルトの Messenger を使う
        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);

            // Messenger 経由で View に MessageBox の表示を要求する。
            MessengerInstance.Send(new DialogMessage(this, greet, result =>
            {
                // MessageBos を表示した後の処理をここに書く
                Name = string.Empty;
            }));
        }

        // 挨拶コマンドを実行できるか判定する。
        private bool CanGreetCommandExecute()
        {
            return !string.IsNullOrEmpty(Name);
        }
    }
}

ViewModel は Messenger に、ダイアログの表示を要求するメッセージを送っています。

次に View を実装します
<Window x:Class="MvvmLightSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{StaticResource MainViewModel}">
    <StackPanel>
        <Label Content="名前"/>
        <TextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"/>
        <Button Content="あいさつする!"
                Command="{Binding Path=GreetCommand}"/>
    </StackPanel>
</Window>

TextBox と Button を配置した、最小限の UI にしています。

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

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

            // Messenger 経由で ViewModel から UI 操作を要求されたとき
            // 行う処理を登録しておく。
            Messenger.Default.Register(this, (Action<DialogMessage>)ShowMessage);
        }

        private void ShowMessage(DialogMessage msg)
        {
            var result = MessageBox.Show(msg.Content);
            msg.Callback(result);
        }
    }
}

ダイアログの表示を要求するメッセージが送られてきたときに実行するコールバックを、Messenger に登録しています。

Messenger はメッセージを受信すると、メッセージの型やメッセージ送信元をもとに、登録されているコールバックの中から該当するものを探して実行します。

App も実装しておきます

View にバインドする 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>

コードビハインドに ViewModel のインスタンスを生成するコードを書きたくなかったので。

実行してみます

f:id:griefworker:20110218115206j:image
ボタンをクリックすると
f:id:griefworker:20110218115217j:image
ちゃんと表示された!

おわりに

今回は MessageBox を表示するだけでしたが、他にも例えば、フォーカスを移動したり、画面を遷移したりする処理も、この Messenger パターンで実装できます。

上記に対して、今までは ViewModel に View のインタフェースを持たせたり、ViewModel 専用のダイアログ表示ヘルパーをこしらえたりと、バラバラのアプローチをとっていました。これらを Messenger パターンで統一できるのは嬉しい。

なお、「メッセージが送られてきたときに実行する処理をコードビハインドで書きたくない」という人たちのために、ビヘイビアを使う改良版があるようです。この方法についても調べなければ。