При работе с WPF/Silverlight, периодически приходится создавать кастомные DependencyProperty, в основном при создании контролов. Стандартный подход объявления и работы с ними не идеальный и имеет минусы, о которых будет сказано ниже. Соответственно, появилась идея упростить запись регистрации и работы с DependencyProperty.
Для начала приведу стандартный код объявления DependencyProperty:
public class SomeDependecyObject : DependencyObject
{
public static readonly DependencyProperty IntValueProperty =
DependencyProperty.Register("IntValue", typeof(int), typeof(SomeDependecyObject), new UIPropertyMetadata(1, OnIntValuePropertyChanged));
public int IntValue
{
get { return (int)GetValue(IntValueProperty); }
set { SetValue(IntValueProperty, value); }
}
private static void OnIntValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
int newPropertyValue = (int)e.NewValue;
SomeDependecyObject instance = (SomeDependecyObject)d;
// Perform callback action.
}
}
Недостатки данного подхода:
- Указание имени свойства в виде строки. При переименовании свойства нужно не забыть переименовать строковое значение.
- Статический callback. Для доступа к членам класса нужно параметр d приводить к типу класса. Новое и старое значения свойства также не приведенные к типу свойства.
- На уровне компиляции нет проверки типа свойства и значения по умолчанию. Параметр propertyType метода Register может принять любой Type. Параметр defaultValue является объектом.
Код улучшенного варианта:
public class SomeDependecyObject : DependencyObject
{
public static readonly DependencyProperty IntValueProperty =
DependencyProperty<SomeDependecyObject>.Register(x => x.IntValue, 1, x => x.OnIntValuePropertyChanged);
public int IntValue
{
get { return (int)GetValue(IntValueProperty); }
set { SetValue(IntValueProperty, value); }
}
private void OnIntValuePropertyChanged(DependencyPropertyChangedEventArgs<int> e)
{
}
}
В данном варианте метод Register принимает свойство в виде выражения, значение по умолчанию конкретного типа и не статический колбек. Метод колбека принимает экземпляр дженерик класса DependencyPropertyChangedEventArgs, где старое и новое значения свойства приведены к типу свойства. Сама запись также упростилась. Далее приведу код классов позволяющих применить такую запись.
Код кастомного дженерик класса DependecyProperty:
public static class DependencyProperty<T> where T : DependencyObject
{
public static DependencyProperty Register<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
{
return Register<TProperty>(propertyExpression, default(TProperty), null);
}
public static DependencyProperty Register<TProperty>(Expression<Func<T, TProperty>> propertyExpression, TProperty defaultValue)
{
return Register<TProperty>(propertyExpression, defaultValue, null);
}
public static DependencyProperty Register<TProperty>(Expression<Func<T, TProperty>> propertyExpression, Func<T, PropertyChangedCallback<TProperty>> propertyChangedCallbackFunc)
{
return Register<TProperty>(propertyExpression, default(TProperty), propertyChangedCallbackFunc);
}
public static DependencyProperty Register<TProperty>(Expression<Func<T, TProperty>> propertyExpression, TProperty defaultValue, Func<T, PropertyChangedCallback<TProperty>> propertyChangedCallbackFunc)
{
string propertyName = propertyExpression.RetrieveMemberName();
PropertyChangedCallback callback = ConvertCallback(propertyChangedCallbackFunc);
return DependencyProperty.Register(propertyName, typeof(TProperty), typeof(T), new PropertyMetadata(defaultValue, callback));
}
private static PropertyChangedCallback ConvertCallback<TProperty>(Func<T, PropertyChangedCallback<TProperty>> propertyChangedCallbackFunc)
{
if (propertyChangedCallbackFunc == null)
return null;
return new PropertyChangedCallback((d, e) =>
{
PropertyChangedCallback<TProperty> callback = propertyChangedCallbackFunc((T)d);
if (callback != null)
callback(new DependencyPropertyChangedEventArgs<TProperty>(e));
});
}
}
public delegate void PropertyChangedCallback<TProperty>(DependencyPropertyChangedEventArgs<TProperty> e);
Данный класс в качестве дженерик параметра принимает тип DependencyObject`а и содержит несколько перегруженных методов Register. Метод Register достает из выражения свойства его имя, конвертирует колбек и создает DependencyProperty стандартным методом.
Код класса DependecyPropertyChangedEventArgs:
public class DependencyPropertyChangedEventArgs<T> : EventArgs
{
public DependencyPropertyChangedEventArgs(DependencyPropertyChangedEventArgs e)
{
NewValue = (T)e.NewValue;
OldValue = (T)e.OldValue;
Property = e.Property;
}
public T NewValue { get; private set; }
public T OldValue { get; private set; }
public DependencyProperty Property { get; private set; }
}
Код дополнительного класса ExpressionExtensions, который используется для получения имени свойства по выражению:
public static class ExpressionExtensions
{
public static string RetrieveMemberName<TArg, TRes>(this Expression<Func<TArg, TRes>> propertyExpression)
{
MemberExpression memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression == null)
{
UnaryExpression unaryExpression = propertyExpression.Body as UnaryExpression;
if (unaryExpression != null)
memberExpression = unaryExpression.Operand as MemberExpression;
}
if (memberExpression != null)
{
ParameterExpression parameterExpression = memberExpression.Expression as ParameterExpression;
if (parameterExpression != null && parameterExpression.Name == propertyExpression.Parameters[0].Name)
return memberExpression.Member.Name;
}
throw new ArgumentException("Invalid expression.", "propertyExpression");
}
}
Автор: shev986