概要

これまでの3回に渡り、MVVMパターンで必須と思われるクラスの実装をして、それを使い簡単な四則演算を行うアプリケーションを作成しました。今回は、これまでこつこつ作成してきたクラスを捨ててMVVMライブラリを使用して四則演算アプリケーションを書き直します。

過去の記事は、TOPICSのMVVM入門から確認できます。

MVVMライブラリ

世間には、既にMVVMをサポートするためのライブラリがいくつか存在しています。今回の記事で取り上げるライブラリは以下のものになります。

どちらもCodePlexで公開されています。

Prism

PrismはMicrosoftのpatterns & practicesで開発された複合アプリケーションを作成するためのフレームワークです。そのためもともとMVVMをサポートするためのライブラリというよりモジュールを組み合わせて1つのアプリケーションを作成するというタイプのアプリケーションを作るためのフレームワークとして開発されていました。今回のバージョン4から、MVVMをサポートするクラスがいくつか追加されています。

これらのクラスを使うことでMVVMパターンでアプリケーションを組むさいにサポートしてくれます。もちろん複合型のアプリケーションを作るという本来のPrismの機能を組み合わせて使うことも出来ます。そこまでの機能が必要ない場合は、ここで紹介したクラスを使うだけでもMVVMパターンのアプリケーションを作成することが出来ます。

MVVM Light Toolkit

MVVM Light Toolkitは、恐らく今一番勢いに乗ってると思われるMVVMライブラリです。CodePlexのページからだけで13000ダウンロード以上されてるという勢いっぷりです。このライブラリは以下のようなクラスを提供しています。

このほかにMVVM Light Toolkitではプロジェクトテンプレート、アイテムテンプレート、コードスニペットなどを提供しています。定型的な記述が多くなりがちなMVVMの開発をサポートしてくれます。

四則演算アプリケーションの改修

上記のライブラリを使用して、これまでに作成してきた四則演算アプリケーションを書き換えます。

Prism

まず、Prismを使用して書き換えを行います。サンプルソースのMVVMCalc.Prismが最終的なコードになります。ここでは、ポイントになる部分を抜粋して解説します。

ViewModelBaseクラス

Prismでは、ViewModelでの妥当性検証の機能を実装した基本クラスは定義されていないため、NotificationObjectを継承する形でViewModelBaseを書き換えます。そして、IDataErrorInfoの実装にErrorsContainerを使用します。コードは以下のようになります。

C#
namespace MVVMCalc.Common 
{ 
    using System; 
    using System.ComponentModel; 
    using System.Linq; 
    using Microsoft.Practices.Prism.ViewModel; 
 
    /// <summary> 
    /// ViewModelの基本クラス。 
    /// INotifyPropertyChangedの実装はPrismのNotificationObjectを使用。 
    /// IDataErrorInfoの実装にはPrismのErrorsContainerを使用して簡略化。 
    /// </summary> 
    public class ViewModelBase : NotificationObject, IDataErrorInfo 
    { 
        private ErrorsContainer<string> errors; 
 
        /// <summary> 
        /// 検証エラーの情報を格納するコンテナを取得する。 
        /// </summary> 
        protected ErrorsContainer<string> Errors 
        { 
            get 
            { 
                if (this.errors == null) 
                { 
                    // エラーの内容に変更があったときはHasErrorプロパティの変更通知を行う 
                    this.errors = new ErrorsContainer<string>( 
                        s => this.RaisePropertyChanged(() => HasError)); 
                } 
 
                return errors; 
            } 
        } 
 
        /// <summary> 
        /// 使用しないため未実装 
        /// </summary> 
        string IDataErrorInfo.Error 
        { 
            get { throw new NotSupportedException(); } 
        } 
 
        /// <summary> 
        /// プロパティのエラーメッセージを取得する。 
        /// </summary> 
        /// <param name="columnName">プロパティ名</param> 
        /// <returns>エラーメッセージ。エラーがないときにはnullを返す。</returns> 
        public string this[string columnName] 
        { 
            get  
            { 
                return this.Errors.GetErrors(columnName).FirstOrDefault(); 
            } 
        } 
 
        /// <summary> 
        /// エラーがある場合にtrueを返す。 
        /// </summary> 
        public bool HasError 
        { 
            get 
            { 
                return this.Errors.HasErrors; 
            } 
        } 
    } 
} 
 
 
元のコードと比べると、IDataErrorInfoの実装のためのコードがErrorsContainerに委譲する形になっているためコード量が減っています。

MainViewModelクラス

MainViewModelクラスは上記で作成したViewModelBaseを継承して作成します。基本的にはこれまで作成したコードがそのまま使用できますが、以下のような特徴があります。

  1. ExpressionTreeによるRaisePropertyChangedのプロパティ名の指定
    プロパティの変更イベントを発行するさいに文字列でプロパティ名を指定するのではなく、RaisePropertyChanged(() => Lhs);のようにラムダ式を指定することができます。これによってインテリセンスの支援を受けれる他に、リファクタリングによるプロパティ名の変更に対応出来ます。
  2. ErrorsContainerによるエラー情報の管理
    ViewModelBaseが提供するErrorsContainerを返すErrorsプロパティを通じて検証エラーの情報をセットしたりクリアすることが出来ます。ここでもプロパティ名の指定にラムダ式が使用できます。
  3. MessengerクラスのかわりにInteractionRequest<Confirmation>を使用
    これまで作成してきたプログラムのMessengerにあたるInteractionRequestクラスを使用します。Confirmationクラスはboolを返すタイプのメッセージとして定義されているので、今回のようにOK/Cancelを判定するような時に使用します。
  4. DelegateCommandのCanExecuteChangedイベントの発行
    PrismのDelegateCommandはCanExecuteChangedイベントの発行をViewModelで責任を持ってやる必要があります。これまで作成してきたサンプルでは、CommandManagerに任せていましたがPrismの実装がそうなっていないため仕方ありません。個人的には改善してほしい点です。(Silverlightとコードを共有することを考えると、有りな判断だとは思われますが・・・) 

C#
namespace MVVMCalc.ViewModel 
{ 
    using System.Collections.Generic; 
    using System.Linq; 
    using Microsoft.Practices.Prism.Commands; 
    using Microsoft.Practices.Prism.Interactivity.InteractionRequest; 
    using MVVMCalc.Common; 
    using MVVMCalc.Model; 
 
    /// <summary> 
    /// MainViewのViewModel 
    /// </summary> 
    public class MainViewModel : ViewModelBase 
    { 
        private string lhs; 
 
        private string rhs; 
 
        private double answer; 
 
        private CalculateTypeViewModel selectedCalculateType; 
 
        private DelegateCommand calculateCommand; 
 
        private InteractionRequest<Confirmation> errorRequest = new InteractionRequest<Confirmation>(); 
 
        public MainViewModel() 
        { 
            this.CalculateTypes = CalculateTypeViewModel.Create().ToArray(); 
 
            // プロパティを初期化して妥当性検証を行う 
            this.InitializeProperties(); 
        } 
 
        /// <summary> 
        /// 計算方式 
        /// </summary> 
        public IEnumerable<CalculateTypeViewModel> CalculateTypes { getprivate set; } 
 
        /// <summary> 
        /// 現在選択されている計算方式 
        /// </summary> 
        public CalculateTypeViewModel SelectedCalculateType 
        { 
            get 
            { 
                return this.selectedCalculateType; 
            } 
 
            set 
            { 
                this.selectedCalculateType = value; 
                this.RaisePropertyChanged(() => SelectedCalculateType); 
            } 
        } 
 
        /// <summary> 
        /// 計算の左辺値 
        /// </summary> 
        public string Lhs 
        { 
            get 
            { 
                return this.lhs; 
            } 
 
            set 
            { 
                this.lhs = value; 
                if (!this.IsDouble(value)) 
                { 
                    this.Errors.SetErrors( 
                        () => Lhs, 
                        new[] { "数字を入力してください" }); 
                } 
                else 
                { 
                    this.Errors.ClearErrors(() => Lhs); 
                } 
 
                this.RaisePropertyChanged(() => Lhs); 
            } 
        } 
 
        /// <summary> 
        /// 計算の右辺値 
        /// </summary> 
        public string Rhs 
        { 
            get 
            { 
                return this.rhs; 
            } 
 
            set 
            { 
                this.rhs = value; 
                if (!this.IsDouble(value)) 
                { 
                    this.Errors.SetErrors( 
                        () => Rhs, 
                        new[] { "数字を入力してください" }); 
                } 
                else 
                { 
                    this.Errors.ClearErrors(() => Rhs); 
                } 
 
                this.RaisePropertyChanged(() => Rhs); 
            } 
        } 
 
        /// <summary> 
        /// 計算結果 
        /// </summary> 
        public double Answer 
        { 
            get 
            { 
                return this.answer; 
            } 
 
            set 
            { 
                this.answer = value; 
                this.RaisePropertyChanged(() => Answer); 
            } 
        } 
 
        /// <summary> 
        /// 計算処理のコマンド 
        /// </summary> 
        public DelegateCommand CalculateCommand 
        { 
            get 
            { 
                if (this.calculateCommand == null) 
                { 
                    this.calculateCommand = new DelegateCommand(CalculateExecute, CanCalculateExecute); 
                } 
 
                return this.calculateCommand; 
            } 
        } 
 
        /// <summary> 
        /// 計算結果にエラーがあったことを通知するメッセージを送信するメッセンジャーを取得する。 
        /// </summary> 
        public InteractionRequest<Confirmation> ErrorRequest 
        { 
            get 
            { 
                return this.errorRequest; 
            } 
        } 
 
        protected override void RaisePropertyChanged(string propertyName) 
        { 
            // 明示的な変更通知が必要 
            this.CalculateCommand.RaiseCanExecuteChanged(); 
            base.RaisePropertyChanged(propertyName); 
        } 
 
        /// <summary> 
        /// 計算処理のコマンドの実行を行います。 
        /// </summary> 
        private void CalculateExecute() 
        { 
            // 現在の入力値を元に計算を行う 
            var calc = new Calculator(); 
            this.Answer = calc.Execute( 
                double.Parse(this.Lhs), 
                double.Parse(this.Rhs), 
                this.SelectedCalculateType.CalculateType); 
 
            if (this.IsInvalidAnswer()) 
            { 
                // 計算結果が実数の範囲から外れてる場合はViewに通知する 
                this.ErrorRequest.Raise( 
                    new Confirmation 
                    { 
                        Title = "確認", 
                        Content = "計算結果が実数の範囲を超えました。入力値を初期化しますか?" 
                    }, 
                    r => 
                    { 
                        // Viewから入力を初期化すると指定された場合はプロパティの初期化を行う 
                        if (!r.Confirmed) 
                        { 
                            return; 
                        } 
 
                        InitializeProperties(); 
                    }); 
            } 
        } 
 
        /// <summary> 
        /// 計算処理が実行可能かどうかの判定を行います。 
        /// </summary> 
        /// <returns></returns> 
        private bool CanCalculateExecute() 
        { 
            // 現在選択されている計算方法がNone以外かつ入力にエラーがなければコマンドの実行が可能 
            return this.SelectedCalculateType.CalculateType != CalculateType.None 
                && !this.HasError; 
        } 
 
        /// <summary> 
        /// Answerが有効な実装値か確認する。 
        /// </summary> 
        /// <returns>有効な実数の範囲にある場合はtrueを返す</returns> 
        private bool IsInvalidAnswer() 
        { 
            return double.IsInfinity(this.Answer) || double.IsNaN(this.Answer); 
        } 
 
        /// <summary> 
        /// プロパティの初期化を行う。 
        /// </summary> 
        private void InitializeProperties() 
        { 
            this.Lhs = string.Empty; 
            this.Rhs = string.Empty; 
            this.Answer = default(double); 
            this.SelectedCalculateType = this.CalculateTypes.First(); 
        } 
 
        /// <summary> 
        /// valueがdouble型に変換できるかどうか検証します。 
        /// </summary> 
        /// <param name="value"></param> 
        /// <returns>doubleに変換できる場合はtrueを返します。</returns> 
        private bool IsDouble(string value) 
        { 
            var temp = default(double); 
            return double.TryParse(valueout temp); 
        } 
    } 
} 
 
 

ConfirmActionクラス

Prismでは、ViewModelからViewにメッセージを通知するための仕組みと、View側でそのメッセージを受け取るTriggerクラスまでは提供していますが、肝心のAction系のクラスは1つも定義されていません。そのためActionクラスを自分で作成する必要があります。基本的にこれまでのサンプルと同じような流れで、Confirmationクラスを前提に書き直しています。

C#
namespace MVVMCalc.Common 
{ 
    using System.Windows; 
    using System.Windows.Interactivity; 
    using Microsoft.Practices.Prism.Interactivity.InteractionRequest; 
 
    /// <summary> 
    /// 確認ダイアログを表示するアクション 
    /// </summary> 
    public class ConfirmAction : TriggerAction<DependencyObject> 
    { 
        protected override void Invoke(object parameter) 
        { 
            // InteractionRequestedEventArgs意外の場合は何もしない 
            var args = parameter as InteractionRequestedEventArgs; 
            if (args == null) 
            { 
                return; 
            } 
 
            // Confirmation意外の場合は何もしない 
            var context = args.Context as Confirmation; 
            if (context == null) 
            { 
                return; 
            } 
 
            // メッセージボックスを表示して 
            var result = MessageBox.Show( 
                args.Context.Content.ToString(),  
                args.Context.Title,  
                MessageBoxButton.OKCancel); 
 
            // ボタンの押された結果をResponseに格納して 
            context.Confirmed = result == MessageBoxResult.OK; 
            // コールバックを呼ぶ 
            args.Callback(); 
        } 
    } 
} 
 
 
このActionを使うためにViewでのTriggerの定義は以下のようなXAMLになります。
XAML
<i:Interaction.Triggers    <prism:InteractionRequestTrigger SourceObject="{Binding Path=ErrorRequest}"        <common:ConfirmAction /> 
    </prism:InteractionRequestTrigger> 
</i:Interaction.Triggers> 
 
 
 

以上でPrism版での主なコードの変更点は終わりです。

MVVM Light Toolkit

次に、MVVM Light Toolkitを使用してコードを書き換えてみます。MVVM Light Toolkitについてはあまり詳しくないため、作法に反した実装をしている可能性があります。そのため、誤った実装をしている場合は、コメントください。適時修正していきたいと思います。サンプルプログラムのMVVMCalc.MVVMLightが最終的なコードになります。

ViewModelLocatorクラス

MVVM Light Toolkitで作成したアプリケーションの特徴は、プロジェクトの形をある程度プロジェクトテンプレートで決めているという点があげられます。主な特徴としては、ViewModelLocatorクラスというクラスを定義して、ここでViewModelクラスのインスタンスを提供するという形になっている点です。コードは以下のようになります。

C#
namespace MVVMCalc.ViewModel 
{ 
    using GalaSoft.MvvmLight; 
 
    /// <summary> 
    /// This class contains static references to all the view models in the 
    /// application and provides an entry point for the bindings. 
    /// </summary> 
    public class ViewModelLocator 
    { 
        private static MainViewModel _main; 
 
        /// <summary> 
        /// Initializes a new instance of the ViewModelLocator class. 
        /// </summary> 
        public ViewModelLocator() 
        { 
            _main = new MainViewModel(); 
        } 
 
        /// <summary> 
        /// Gets the Main property which defines the main viewmodel. 
        /// </summary> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", 
            "CA1822:MarkMembersAsStatic", 
            Justification = "This non-static member is needed for data binding purposes.")] 
        public MainViewModel Main 
        { 
            get 
            { 
                return _main; 
            } 
        } 
    } 
} 
 
今回のサンプルでは、MainViewModelしか提供していませんが、必要に応じてここでViewModelを提供するプロパティを作成します。このViewModelLocatorはApp.xamlでリソースに登録して使用します。
XAML
<Application 
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             xmlns:vm="clr-namespace:MVVMCalc.ViewModel" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero" mc:Ignorable="d" x:Class="MVVMCalc.App" 
             Startup="Application_Startup"    <Application.Resources        ...省略...         
        <!--Global View Model Locator--> 
        <vm:ViewModelLocator x:Key="Locator" 
                            d:IsDataSource="True" /> 
    </Application.Resources> 
</Application> 
 
 
こうすることでMainView.xamlのDataContextにMainViewModelを設定するXAMLは以下のようになります。
XAML
DataContext="{Binding Main, Source={StaticResource ResourceKey=Locator}}" 
 
 

ViewModelBaseクラス

ViewModelBaseクラスはMVVM Light Toolkitで同名のクラスが提供されていますが、入力値の妥当性検証の機能は提供されていないため、MVVM Light ToolkitのViewModelBaseを継承して、このアプリケーション用のViewModelBaseクラスを定義します。コードは以下のようになります。Prismと違って、IDataErrorInfoを実装するためのヘルパークラスは存在しないため、これまで作ってきたコードと、ほぼ同じコードになります。
異なる点は、MVVM Light Toolkitの提供するコードスニペットでプロパティを定義しているため、プロパティが少し癖のあるコードになっています。
C#
namespace MVVMCalc.ViewModel 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Windows; 
    using GalaSoft.MvvmLight.Command; 
    using GalaSoft.MvvmLight.Messaging; 
    using MVVMCalc.Common; 
    using MVVMCalc.Model; 
 
    /// <summary> 
    /// MainViewのViewModel 
    /// </summary> 
    public class MainViewModel : ViewModelBase 
    { 
        /// <summary> 
        /// 計算結果にエラーが起きた場合に送信するメッセージのトークン 
        /// </summary> 
        public static readonly Guid ErrorMessageToken = Guid.NewGuid(); 
 
        /// <summary> 
        /// The <see cref="Lhs" /> property's name. 
        /// </summary> 
        public const string LhsPropertyName = "Lhs"; 
 
        /// <summary> 
        /// The <see cref="Rhs" /> property's name. 
        /// </summary> 
        public const string RhsPropertyName = "Rhs"; 
 
        /// <summary> 
        /// The <see cref="Answer" /> property's name. 
        /// </summary> 
        public const string AnswerPropertyName = "Answer"; 
 
        private string lhs; 
 
        private string rhs; 
 
        private double answer; 
 
        private CalculateTypeViewModel selectedCalculateType; 
 
        private RelayCommand calculateCommand; 
 
        public MainViewModel() 
        { 
            this.CalculateTypes = CalculateTypeViewModel.Create().ToArray(); 
 
            // プロパティを初期化して妥当性検証を行う 
            this.InitializeProperties(); 
        } 
 
        /// <summary> 
        /// 計算方式 
        /// </summary> 
        public IEnumerable<CalculateTypeViewModel> CalculateTypes { getprivate set; } 
 
        /// <summary> 
        /// 現在選択されている計算方式 
        /// </summary> 
        public CalculateTypeViewModel SelectedCalculateType 
        { 
            get 
            { 
                return this.selectedCalculateType; 
            } 
 
            set 
            { 
                this.selectedCalculateType = value; 
                this.RaisePropertyChanged("SelectedCalculateType"); 
            } 
        } 
 
        /// <summary> 
        /// 計算の左辺値 
        /// </summary> 
        public string Lhs 
        { 
            get 
            { 
                return lhs; 
            } 
 
            set 
            { 
                if (lhs == value) 
                { 
                    return; 
                } 
 
                var oldValue = lhs; 
                lhs = value; 
 
                // 入力値の検証 
                if (!this.IsDouble(value)) 
                { 
                    this.SetError("Lhs""数字を入力してください"); 
                } 
                else 
                { 
                    this.ClearError("Lhs"); 
                } 
 
                // Update bindings, no broadcast 
                RaisePropertyChanged(LhsPropertyName); 
 
                // Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging 
                RaisePropertyChanged(LhsPropertyName, oldValue, valuetrue); 
            } 
        } 
 
        /// <summary> 
        /// 計算の右辺値 
        /// </summary> 
        public string Rhs 
        { 
            get 
            { 
                return rhs; 
            } 
 
            set 
            { 
                if (rhs == value) 
                { 
                    return; 
                } 
 
                var oldValue = rhs; 
                rhs = value; 
 
                // 入力値の検証 
                if (!this.IsDouble(value)) 
                { 
                    this.SetError("Rhs""数字を入力してください"); 
                } 
                else 
                { 
                    this.ClearError("Rhs"); 
                } 
 
                // Update bindings, no broadcast 
                RaisePropertyChanged(RhsPropertyName); 
 
                // Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging 
                RaisePropertyChanged(RhsPropertyName, oldValue, valuetrue); 
            } 
        } 
 
        /// <summary> 
        /// 計算結果 
        /// </summary> 
        public double Answer 
        { 
            get 
            { 
                return answer; 
            } 
 
            set 
            { 
                if (answer == value) 
                { 
                    return; 
                } 
 
                var oldValue = answer; 
                answer = value; 
 
                // Update bindings, no broadcast 
                RaisePropertyChanged(AnswerPropertyName); 
 
                // Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging 
                RaisePropertyChanged(AnswerPropertyName, oldValue, valuetrue); 
            } 
        } 
 
        /// <summary> 
        /// 計算処理のコマンド 
        /// </summary> 
        public RelayCommand CalculateCommand 
        { 
            get 
            { 
                if (this.calculateCommand == null) 
                { 
                    this.calculateCommand = new RelayCommand(CalculateExecute, CanCalculateExecute); 
                } 
 
                return this.calculateCommand; 
            } 
        } 
 
        /// <summary> 
        /// 計算処理のコマンドの実行を行います。 
        /// </summary> 
        private void CalculateExecute() 
        { 
            // 現在の入力値を元に計算を行う 
            var calc = new Calculator(); 
            this.Answer = calc.Execute( 
                double.Parse(this.Lhs), 
                double.Parse(this.Rhs), 
                this.SelectedCalculateType.CalculateType); 
 
            if (this.IsInvalidAnswer()) 
            { 
                // 計算結果が実数の範囲から外れてる場合はViewに通知する 
                var message = new DialogMessage( 
                    this, 
                    "計算結果が実数の範囲を超えました。入力値を初期化しますか?", 
                    result => 
                    { 
                        if (result != MessageBoxResult.OK) 
                        { 
                            return; 
                        } 
 
                        this.InitializeProperties(); 
                    }) 
                { 
                    Caption = "確認", 
                    Button = MessageBoxButton.OKCancel 
                }; 
 
                Messenger.Default.Send<DialogMessage>( 
                    message, 
                    MainViewModel.ErrorMessageToken); 
            } 
        } 
 
        /// <summary> 
        /// 計算処理が実行可能かどうかの判定を行います。 
        /// </summary> 
        /// <returns></returns> 
        private bool CanCalculateExecute() 
        { 
            // 現在選択されている計算方法がNone以外かつ入力にエラーがなければコマンドの実行が可能 
            return this.SelectedCalculateType.CalculateType != CalculateType.None 
                && !this.HasError; 
        } 
 
        /// <summary> 
        /// Answerが有効な実装値か確認する。 
        /// </summary> 
        /// <returns>有効な実数の範囲にある場合はtrueを返す</returns> 
        private bool IsInvalidAnswer() 
        { 
            return double.IsInfinity(this.Answer) || double.IsNaN(this.Answer); 
        } 
 
        /// <summary> 
        /// プロパティの初期化を行う。 
        /// </summary> 
        private void InitializeProperties() 
        { 
            this.Lhs = string.Empty; 
            this.Rhs = string.Empty; 
            this.Answer = default(double); 
            this.SelectedCalculateType = this.CalculateTypes.First(); 
        } 
 
        /// <summary> 
        /// valueがdouble型に変換できるかどうか検証します。 
        /// </summary> 
        /// <param name="value"></param> 
        /// <returns>doubleに変換できる場合はtrueを返します。</returns> 
        private bool IsDouble(string value) 
        { 
            var temp = default(double); 
            return double.TryParse(valueout temp); 
        } 
    } 
} 
 
 
その他の特徴としては、DelegateCommandがRelayCommandという名前にかわっている部分とMessengerがデフォルトのインスタンスを使用するため、予想外の箇所でMessageが受信されないようにToken(ここではGUID)を渡して一意に識別できるようにしています。
MessengerのMessageの受信
MVVM Light Toolkitでは、MessengerのMessage用のTriggerが用意されていません。汎用的に作ることは出来ますが、ここでは普通にMVVM Light Toolkitを使う場合のやり方でメッセージの受信処理を書きました。MainView.xaml.csのコードビハインドに以下のコードを記述しています。
C#
namespace MVVMCalc.View 
{ 
    using System.Windows; 
    using GalaSoft.MvvmLight.Messaging; 
    using MVVMCalc.ViewModel; 
 
    /// <summary> 
    /// MainView.xaml の相互作用ロジック 
    /// </summary> 
    public partial class MainView : Window 
    { 
        public MainView() 
        { 
            InitializeComponent(); 
 
            // ViewModelからErrorMessageTokenが渡された時の処理の登録 
            Messenger.Default.Register<DialogMessage>( 
                this, 
                MainViewModel.ErrorMessageToken, 
                m => 
                { 
                    // メッセージを表示して、結果をコールバック 
                    var r = MessageBox.Show(m.Content, m.Caption, m.Button); 
                    m.Callback(r); 
                }); 
        } 
    } 
} 
 
 
 正直な話、あまりコードビハインドに処理を書きたくないのですが仕方ありません。Registerを使いDialogMessageでMainViewModel.ErrorMessageTokenでが指定されて飛んできたメッセージのに対応する処理を書いています。メッセージボックスを表示して、結果をコールバックに渡しています。
書いていて感じたのですが、ダイアログを出すといった特定のViewに依存しない処理は、どこか共通的なクラスに切り出して実装したほうがいいのかもしれません。Viewのコードビハインドに書くのは、そのViewでないと処理できないものにげんていしたほうが良いように感じます。
以上で、MVVM Light Toolkitのコードは終わりです。

まとめ

今回は、これまで自前で実装してきたMVVMライブラリもどきのコードをMVVMライブラリに置き換えて実装しました。一般的には、MVVMライブラリは自前実装するよりも、あるものを利用したほうが工数的にも品質的にも有利になります。特に弱参照を使用したメモリリーク対策がMVVMライブラリには盛り込まれているため、そこまで留意して自作のライブラリをバグなく作るというのは大変な労力がかかります。特に既存のライブラリを使用出来ない明確な理由がないケース以外では、これらのライブラリの利用を検討してみてください。

これまでの入門の1~3では、内部実装がどのようになっているのかイメージをつかんでもらうために全て自前実装で行ってきました。恐らく、これらのコードを理解していれば世間にあるMVVMライブラリのコードを読み、理解して使うための助けになると思います。

次回は、ここでは取り上げていないMVVMライブラリを題材に今回と同じように四則演算アプリケーションを書き換えてみようと思います。