WPF データバインディング項目用のヘルパークラス

WPFでデータバインディングを利用する場合、対象となる項目が変更された場合、その項目の名称をパラメータにして`INotifyPropertyChanged.PropertyChanged`イベントを発生させる必要があります。

プロパティとして実装する場合、以下のような感じになるでしょうか？

```csharp:TestVeiwModel.cs
class TestVeiwModel : ViewModelBase
{
    private string _foo;
    public string Foo
    {
        get { return _foo; }
        set 
        {
            if (_foo != value)
            {
                _foo = value;
                // 値が変更されたらINotifyPropertyChanged.PropertyChangedを発生させる。
                RaisePropertyChanged("Foo");
            }
        }
    }

    public TestVeiwModel()
    {
        CreateDataBindItem<string>("Foo", "", true);
    }
}
```

型が違ったとしても、値が変更されたら`INotifyPropertyChanged.PropertyChanged`を発生させるというのは定形ですので、ジェネリックなヘルパークラスにまとめてみます。

## サンプルコード

以前に作成した[ViewModelの基底クラス](http://qiita.com/hugo-sb/items/f07ef53dea817d198475)へ追加します。

```csharp:ViewModelBase.cs
#region ==== Abstract Class : ViewModelBase 
/// <summary>
/// ViewModelの抽象基底クラス
///    INotifyPropertyChanged, IDataErrorInfo を実装、
///    その他 ViewModel用のヘルパークラス等を提供する
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
{
    //  省略

    #region **** Inner Class : DataBindItemBase
    /// <summary>
    /// データバインド項目抽象基底クラス
    /// </summary>
    protected abstract class DataBindItemBase
    {
        #region **** Members

        /// <summary>
        /// 値が変更されたかどうかを保持する内部変数
        /// </summary>
        private bool _changed;

        #endregion

        #region **** Properties

        /// <summary>
        /// 親となるViewModel基底クラスへの参照 : ViewModelBase
        /// </summary>
        protected ViewModelBase _VM { get; set; }

        /// <summary>
        /// 項目名 : string
        /// </summary>
        public string Name { get; protected set; }

        /// <summary>
        /// 値の型(抽象) : Type
        /// </summary>
        public abstract Type ValueType { get; }

        /// <summary>
        /// 値が変更された？ : bool
        /// </summary>
        public bool Changed
        {
            get { return _changed; }
            protected set
            {
                if (_changed != value)
                {
                    _changed = value;
                    _VM.RaisePropertyChanged("Changed");
                }
            }
        }

        /// <summary>
        /// 不正な値を受け付ける？
        ///    Trueの場合、ValidationCheckでエラーになった値もValueへの代入を認める
        /// </summary>
        public bool AcceptInvalidData { get; set; }

        #endregion

        #region **** Abstract Method : Initialize
        /// <summary>
        /// 初期処理（抽象）
        /// </summary>
        public abstract void Initialize();
        #endregion

        #region **** ClearChanged
        /// <summary>
        /// 項目を未変更状態にする
        /// </summary>
        public void ClearChanged()
        {
            Changed = false;
        }
        #endregion
    }
    #endregion

    #region **** Inner Class : DataBindItem<T> 
    /// <summary>
    /// データバインド項目クラス（ジェネリック）
    /// </summary>
    /// <typeparam name="T">項目の型</typeparam>
    protected class DataBindItem<T> : DataBindItemBase
    {
        #region **** Delegates / Events

        /// <summary>
        /// バリデーションチェック用デリゲート
        /// </summary>
        public Func<T, Result> CheckValidation;

        /// <summary>
        /// 項目変更前イベント
        ///    BeforeValueChangeEventArgs.CancelにTrueをセットすることで
        ///    項目変更処理をキャンセル可能。
        /// </summary>
        public event EventHandler<BeforeValueChangeEventArgs> BeforeValueChange;

        /// <summary>
        /// 項目変更後イベント
        /// </summary>
        public event EventHandler<ValueChangedEventArgs> ValueChanged;

        #endregion

        #region **** Members

        /// <summary>
        /// 項目の値を保持する内部変数
        /// </summary>
        private T _value;

        /// <summary>
        /// 項目の初期値を保持する内部変数
        /// </summary>
        private T _defaultValue;

        #endregion

        #region **** Properties

        /// <summary>
        /// 項目値 : T
        /// </summary>
        public T Value
        {
            get { return _value; }
            set
            {
                // 値が変更されたときのみ処理する
                if (!eq(_value, value))
                {
                    // バリデーションチェック
                    Result result = (CheckValidation != null) ? CheckValidation(value) : Result.ResultSuccess;

                    // バリデーションチェックに成功か、AcceptInvalidDataがTrueの場合
                    if (result.Success || AcceptInvalidData)
                    {

                        // 変更前イベントを発生させる
                        BeforeValueChangeEventArgs arg = new BeforeValueChangeEventArgs(value, _value);
                        if (BeforeValueChange != null)
                            BeforeValueChange(this, arg);

                        // キャンセルされなければ
                        if (!arg.Cancel)
                        {
                            // 値を代入する
                            _value = value;
                            _VM.RaisePropertyChanged(Name);
                            Changed = true;

                            // 変更後イベントを発生させる
                            if (ValueChanged != null)
                                ValueChanged(this, arg);
                        }
                    }

                    // ViewModelに対してバリデーションチェックの結果をセットする
                    _VM.SetError(Name, result);
                }
            }
        }

        /// <summary>
        /// 項目の型 : Type
        /// </summary>
        public override Type ValueType
        {
            get { return _value.GetType(); }
        }

        #endregion

        #region **** Constructor
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="vm">ViewModelへの参照 : ViewModelBase</param>
        /// <param name="name">項目名 : string</param>
        /// <param name="defaultValue">初期値 : T</param>
        public DataBindItem(ViewModelBase vm, string name, T defaultValue)
        {
            if (vm == null || string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException();

            _VM = vm;
            Name = name;
            _value = _defaultValue = defaultValue;
            Changed = false;
            AcceptInvalidData = false;
        }
        #endregion

        #region **** Private Method : eq
        /// <summary>
        /// 値が一致するか？
        ///     Tに対して直接比較演算子を使用できないためIComparableにキャストして比較する
        ///     IComparableでない場合はつねにFalseを返す。
        /// </summary>
        /// <param name="v1">値1 : T</param>
        /// <param name="v2">値2 : T</param>
        /// <returns>結果値 : bool</returns>
        private bool eq(T v1, T v2)
        {
            if (v1 is IComparable)
                return (v1 as IComparable).CompareTo(v2) == 0;
            else
                return false;
        }
        #endregion

        #region **** Method (Override) : Initialize
        /// <summary>
        /// 項目を初期化する
        ///     コンストラクタで指定した初期値がセットされる。
        ///     エラー、変更状態もクリアされる。
        /// </summary>
        public override void Initialize()
        {
            _value = _defaultValue;
            _VM.ClearErrror(Name);
            Changed = false;
        }
        #endregion
    }
    #endregion

    /// <summary>
    /// DataBindItemのコレクション 
    /// </summary>
    protected Dictionary<string, DataBindItemBase> DataBindItems { get; private set; }

    #region **** Constructor
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public ViewModelBase()
    {
        DataBindItems = new Dictionary<string, DataBindItemBase>();
    }
    #endregion

    #region **** Method : CreateDataBindItem(1)
    /// <summary>
    /// データバインド項目の生成
    /// </summary>
    /// <typeparam name="T">項目の型</typeparam>
    /// <param name="name">項目名 : string</param>
    /// <param name="defaultValue">初期値 : T</param>
    /// <param name="checkValidation">バリデーションチェックのデリゲート : Func</param>
    /// <returns>項目オブジェクト : DataBindItem</returns>
    protected DataBindItem<T> CreateDataBindItem<T>(string name, T defaultValue, Func<T, Result> checkValidation = null)
    {
        DataBindItem<T> item = new DataBindItem<T>(this, name, defaultValue);
        if (checkValidation != null)
            item.CheckValidation = checkValidation;
        DataBindItems[name] = item;
        return item;
    }
    #endregion

    #region **** Method : CreateDataBindItem(2)
    /// <summary>
    /// データバインド項目の生成
    /// </summary>
    /// <typeparam name="T">項目の型</typeparam>
    /// <param name="name">項目名 : string</param>
    /// <param name="defaultValue">初期値 : T</param>
    /// <param name="acceptInvalidData">不正な値を受け入れるか？ : bool</param>
    /// <param name="checkValidation">バリデーションチェックのデリゲート : Func</param>
    /// <returns>項目オブジェクト : DataBindItem</returns>
    protected DataBindItem<T> CreateDataBindItem<T>(string name, T defaultValue, bool acceptInvalidData, Func<T, Result> checkValidation = null)
    {
        DataBindItem<T> item = new DataBindItem<T>(this, name, defaultValue);
        item.AcceptInvalidData = acceptInvalidData;
        if (checkValidation != null)
            item.CheckValidation = checkValidation;

        DataBindItems[name] = item;
        return item;
    }
    #endregion

    #region **** Method : GetDataBindItem
    /// <summary>
    /// データバインド項目の取得
    /// </summary>
    /// <typeparam name="T">項目の型</typeparam>
    /// <param name="name">項目名 : string</param>
    /// <returns>項目オブジェクト : DataBindItem</returns>
    protected DataBindItem<T> GetDataBindItem<T>(string name)
    {
        return DataBindItems[name] as DataBindItem<T>;
    }
    #endregion

}
#endregion
```

バリデーションチェック用のデリゲートや値変更時のイベントなどを追加してあるので、少しコードが長くなってしまいました。

## 使い方

利用するクラスを`ViewModelBase`から派生させて、コンストラクタなどから`CreateDataBindItem<T>()`を呼び出して、`DataBindItem`のインスタンスを生成、このインスタンスをプロパティでラップして公開するというイメージになります。

```csharp:TestVeiwModel.cs
class TestVeiwModel : ViewModelBase
{
    public string Foo
    {
        get { return GetDataBindItem<string>("Foo").Value; }
        set { GetDataBindItem<string>("Foo").Value = value; }
    }

    public TestVeiwModel()
    {
        CreateDataBindItem<string>("Foo", "", true);
    }
}
```

生成された`DataBindItem`のインスタンスは、抽象クラス`DataBindItemBase`の`Dictionary`である`DataBindItems`に保持されているので

```csharp
Foreach (var item in DataBindItems.Vlaues)
{
    // 何か処理をする
}
```

という感じで一括処理ができるので便利かも。