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
のインスタンスは、抽象クラスDataBindItemBase
のDictionary
であるDataBindItems
に保持されているので
Foreach (var item in DataBindItems.Vlaues)
{
// 何か処理をする
}
という感じで一括処理ができるので便利かも。