Как создать прокси-сервер привязки данных TwoWay?
У меня есть следующие классы...
class ExpressionBinder<T>
{
public Func<T> Getter;
public Action<T> Setter;
public T Value
{
get { return Getter.Invoke(); }
set { Setter.Invoke(value); }
}
public ExpressionBinder(Func<T> getter, Action<T> setter)
{
Getter = getter;
Setter = setter;
}
}
class ComparisonBinder<TSource, TValue> : ExpressionBinder<bool>, INotifyPropertyChanged
{
private TSource instance;
private TValue comparisonValue;
private PropertyInfo pInfo;
public event PropertyChangedEventHandler PropertyChanged;
public ComparisonBinder(TSource instance, Expression<Func<TSource, TValue>> property, TValue comparisonValue) : base(null,null)
{
pInfo = GetPropertyInfo(property);
this.instance = instance;
this.comparisonValue = comparisonValue;
Getter = GetValue;
Setter = SetValue;
}
private bool GetValue()
{
return comparisonValue.Equals(pInfo.GetValue(instance, null));
}
private void SetValue(bool value)
{
if (value)
{
pInfo.SetValue(instance, comparisonValue,null);
NotifyPropertyChanged("CustomerName");
}
}
private void NotifyPropertyChanged(string pName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pName));
}
}
/// <summary>
/// Adapted from surfen's answer (https://stackoverflow.com/a/10003320/219838)
/// </summary>
private PropertyInfo GetPropertyInfo(Expression<Func<TSource, TValue>> propertyLambda)
{
Type type = typeof(TSource);
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyLambda));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyLambda));
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format(
"Expresion '{0}' refers to a property that is not from type {1}.",
propertyLambda,
type));
return propInfo;
}
}
Мой код, использующий класс ComparisonBinder
, выглядит следующим образом:
radioMale.DataBindings.Add(
"Checked",
new ComparisonBinder<DataClass, GenderEnum>(DataObj, (x) => x.Gender, GenderEnum.Male),
"Value",
false,
DataSourceUpdateMode.OnPropertyChanged);
radioFemale.DataBindings.Add(
"Checked",
new ComparisonBinder<DataClass, GenderEnum>(DataObj, (x) => x.Gender, GenderEnum.Male),
"Value",
false,
DataSourceUpdateMode.OnPropertyChanged);
Они позволяют мне привязать множество элементов управления к одному и тому же свойству, используя выражение для генерации значения для связанного свойства элемента управления. Он прекрасно работает от элемента управления до объекта. (Если вы хотите узнать больше об использовании этого класса и этот вопрос)
А теперь мне нужно сделать наоборот. Когда я обновлю свой объект (что изменит результат выражение get) мне нужно обновить связанный элемент управления. Я попытался реализоватьINotifyPropertyChanged
, но это не имело никакого значения.
Одна странная вещь: установка элементов управления DataSourceUpdateMode
на OnPropertyChanged
каким-то образом останавливает меня от изменения проверенного радио.
1 ответ:
Быстрый ответ:
- Вы должны использовать источник привязки
- реализовать INotifyPropertyChanged в вашем объекте
- заставьте прокси-сервер прослушать событие выше
- реализовать INotifyPropertyChanged в прокси
- фильтруйте события PropertyChanged, полученные от вашего объекта в прокси, и вызывайте только тогда, когда
((PropertyChangedEventArgs) args).PropertyName == pInfo.Name
- Добавьте прокси в
BindSource.DataSource
- используйте
BindingSource
для привязки элемента управления к свойству объекта данныхФинал код
Доступно (наряду с другими классами привязки) по адресу: http://github.com/svallory/BindingTools
Классы
class ExpressionBinder<T> : INotifyPropertyChanged { public Func<T> Getter; public Action<T> Setter; public event PropertyChangedEventHandler PropertyChanged; protected IList<string> WatchingProps; public T Value { get { return Getter.Invoke(); } set { Setter.Invoke(value); } } public ExpressionBinder(Func<T> getter, Action<T> setter) { WatchingProps = new List<string>(); Getter = getter; Setter = setter; } public ExpressionBinder(Func<T> getter, Action<T> setter, ref PropertyChangedEventHandler listenToChanges, IList<string> propertyNames) { Getter = getter; Setter = setter; listenToChanges += SourcePropertyChanged; WatchingProps = propertyNames; } protected void SourcePropertyChanged(object obj, PropertyChangedEventArgs args) { if (PropertyChanged != null && WatchingProps.Contains(args.PropertyName)) { PropertyChanged(this, args); } } protected PropertyInfo GetPropertyInfo<TSource, TValue>(Expression<Func<TSource, TValue>> propertyLambda) { Type type = typeof(TSource); var member = propertyLambda.Body as MemberExpression; if (member == null) throw new ArgumentException(string.Format( "Expression '{0}' refers to a method, not a property.", propertyLambda)); var propInfo = member.Member as PropertyInfo; if (propInfo == null) throw new ArgumentException(string.Format( "Expression '{0}' refers to a field, not a property.", propertyLambda)); if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType)) throw new ArgumentException(string.Format( "Expresion '{0}' refers to a property that is not from type {1}.", propertyLambda, type)); return propInfo; } } class ComparisonBinder<TSource, TValue> : ExpressionBinder<bool> where TSource : INotifyPropertyChanged { private readonly TSource instance; private readonly TValue comparisonValue; private readonly PropertyInfo pInfo; public ComparisonBinder(TSource instance, Expression<Func<TSource, TValue>> property, TValue comparisonValue) : base(null, null) { pInfo = GetPropertyInfo(property); this.instance = instance; this.comparisonValue = comparisonValue; Getter = GetValue; Setter = SetValue; instance.PropertyChanged += SourcePropertyChanged; WatchingProps.Add(pInfo.Name); } private bool GetValue() { return comparisonValue.Equals(pInfo.GetValue(instance, null)); } private void SetValue(bool value) { if (value) { pInfo.SetValue(instance, comparisonValue, null); } } }
Использование
var bs = new BindingSource(); bs.DataSource = new ComparisonBinder<MySourceObjClass, PropType>( MySourceObject, p => p.PropertyToBind, ValueToCompare); rdBeSmart.DataBindings.Add( "Checked", bs, "Value");