はじめに
以前、MVVM パターンで ViewModel から View を操作する話題で祭になりました。
- MVVM パターンで VM から VIEW を操作したい
- MVVMでVMからViewを操作する考察 - The Road to C# Master Trapemiya
- MVVMパターンでVMからVに通知する方法 - かずきのBlog@Hatena
- MVVM パターンで ViewModel から Viewを操作する方法 - present
その後しばらく WPF や Silverlight から離れていたんですが、その間に Messenger というパターンが有力になってきたみたいです。さっそく調査開始。
Messenger パターンって?
簡単に説明すると、View と ViewModel が共通の Messenger を参照し、ViewModel は Messenger を介して View に UI 操作をリクエストする、というパターン。図にするとこんな感じです。
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 のインスタンスを生成するコードを書きたくなかったので。
おわりに
今回は MessageBox を表示するだけでしたが、他にも例えば、フォーカスを移動したり、画面を遷移したりする処理も、この Messenger パターンで実装できます。
上記に対して、今までは ViewModel に View のインタフェースを持たせたり、ViewModel 専用のダイアログ表示ヘルパーをこしらえたりと、バラバラのアプローチをとっていました。これらを Messenger パターンで統一できるのは嬉しい。
なお、「メッセージが送られてきたときに実行する処理をコードビハインドで書きたくない」という人たちのために、ビヘイビアを使う改良版があるようです。この方法についても調べなければ。