LoginSignup
18
17

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-04-02

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

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

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の基底クラスへ追加します。

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のインスタンスを生成、このインスタンスをプロパティでラップして公開するというイメージになります。

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のインスタンスは、抽象クラスDataBindItemBaseDictionaryであるDataBindItemsに保持されているので

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

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

18
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
17