生成したViewModelとバリデーション

今までのおさらいになるが、T4 Templateと属性のおかげで、簡単な属性をメタデータとして与えることでフル装備のViewModelを簡単に自動生成できるようになった。

MyViewModel.cs (メタデータを設定する生成元)
using System;
using Mandarine.MVVM.Annotation;

namespace DataBindApp1.ViewModel
{
    [PropertyDecl("Name", typeof(String))]
    [PropertyDecl("Age", typeof(int), defaultValue = 20)]
    public partial class MyViewModel
    {
    }
}

このようにメタデータを記述したクラスを予め用意しておくことで、本来個別に起こす必要があるViewModelクラスを自動的に生成することができる。

MyViewModel_Generated.cs (ViewModelGenerate.ttにより自動生成された側)
using System;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;

using Mandarine.MVVM.ViewModel;

namespace DataBindApp1.ViewModel
{
    /// <summary>
    /// このクラスはE:\.Mandarine\Mandarine\CodeGen\ViewModelGenerate.ttにより
    /// 2011/10/05 17:49:19に自動生成されました。
    /// このファイルをエディタで直接編集しないでください
    /// </summary>
    public partial class MyViewModel: Mandarine.MVVM.ViewModel.ViewModel
    {
        #region 

        private String _name ;

        public String Name
        {
            get { return _name ; }
            set 
            { 
                if ( _name == value ) 
                    return;
                _name = value; 
                NotifyPropertyChanged(() => Name);
            }
        }

        #endregion
        #region 

        private int _age = 20;

        public int Age
        {
            get { return _age ; }
            set 
            { 
                if ( _age == value ) 
                    return;
                _age = value; 
                NotifyPropertyChanged(() => Age);
            }
        }

        #endregion
    }
}

内容としては与えられたメタデータから必要なプロパティとアクセサ(get;set)を生成し、更にデータバインドで必要なプロパティ変更通知を呼び出す処理が記述されている。
このViewModelをViewとバインドすることで、ViewのControl(この場合はTextBox)のプロパティとの同期を実現できる。

ここまでの方法によりViewModelの定型的なコーディングを省くことができるのでかなり楽になったのだが、ここから更に効率化するためにこのViewModelに実施するバリデーションを組込んでいこうと思う。


以前にも書いたが、WindowsPhone7のバリデーションは Silverlight/WPFと比べても最も簡易なもので、よくある例はプロパティのセッター中でバリデーションを行い、通知イベントを発生させるために例外を起こす方法である。

プロパティのセッターでバリデーションを実行する
    public String Name
    {
        get { return _name ; }
        set 
        { 
            if ( _name == value ) 
                return;
                
            if (string.IsNullOrEmpty(value))
            {
                throw new ArgumentException("名前は必須ですよ");
            }
            _name = value; 
            NotifyPropertyChanged(() => Name);
        }
    }

XAMLでこのプロパティをコントロールのTextプロパティにデータバインドする際に、NotifyOnValidationErrorプロパティ及びValidatesOnExceptionsをtrueにすることで、バインドしたプロパティのセッターが動作してArgumentExceptionがスローされると、FrameworkElement.BindingValidationError イベントが発火するので、アプリケーションではこのイベントを捕捉することでエラー処理を行うことができる。

XAMLのデータバインディング構文でBindingValidationErrorイベントを有効にする
<TextBox Height="72" HorizontalAlignment="Left" Margin="65,6,0,0" x:Name="txtName" VerticalAlignment="Top" Width="381" Text="{Binding Name, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"/>

一方、Silverlight3ではSystem.ComponentModel.DataAnnotationsネームスペースを使った属性ベースのバリデーションを実装することができる。
上記相当のバリデーションを属性ベースで実装した場合、以下のようなコードになるだろう。(想像というか予定である)

属性ベースのバリデーション
    [Required] // このプロパティに適用するバリデート制約(必須)
    public String Name
    {
        get { return _name ; }
        set 
        { 
            ValidateProperty("Name", value); //バリデータによるバリデート
            if ( _name == value ) 
                return;
                
            _name = value; 
            NotifyPropertyChanged(() => Name);
        }
    }

[Required]はRequiredAttributeクラスであり、System.ComponentModel.DataAnnotationsに用意されたバリデーション処理のための属性クラスである。この属性を設定したプロパティに対して必須制約属性を課し、入力が無かった場合にはエラーとするのである。

幸い同ネームスペースのコードはWindowsPhone7でのコンパイル、動作させることができそうなので、これをそのまま利用して属性ベースのバリデーションを組込んでみようと思う。

次回以降、この処理をどのように実現していくか(どこまで自動生成するか)を考えていこう。