Simplification of registration and work with DependencyProperty
When working with WPF / Silverlight, you periodically have to create custom DependencyProperty, mainly when creating controls. The standard approach to advertising and working with them is not ideal and has disadvantages, which will be discussed below. Accordingly, an idea appeared to simplify the recording of registration and work with DependencyProperty.
To get started, here’s the standard DependencyProperty declaration code:
The disadvantages of this approach:
Enhanced Option Code:
In this variant, the Register method accepts a property in the form of an expression, a default value of a specific type and not a static callback. The callback method accepts an instance of the generic of the DependencyPropertyChangedEventArgs class, where the old and new property values are cast to the property type. The recording itself was also simplified. Next, I will give the code of classes that allow applying such a record.
Custom generic code for DependecyProperty class:
This class as a generic parameter takes the type DependencyObject and contains several overloaded Register methods. The Register method takes its name from the property expression, converts the callback and creates the DependencyProperty using the standard method.
DependecyPropertyChangedEventArgs class code:
The code for the optional ExpressionExtensions class, which is used to get the property name by expression:
To get started, here’s the standard DependencyProperty declaration code:
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.
}
}
The disadvantages of this approach:
- Specifies the name of the property as a string. When renaming a property, remember to rename the string value.
- Static callback. To access class members, the parameter d must be cast to the class type. The new and old property values are also not typecast properties.
- At compilation level, there is no verification of the type of the property and the default value. The propertyType parameter of the Register method can accept any Type. The defaultValue parameter is an object.
Enhanced Option Code:
public class SomeDependecyObject : DependencyObject
{
public static readonly DependencyProperty IntValueProperty =
DependencyProperty.Register(x => x.IntValue, 1, x => x.OnIntValuePropertyChanged);
public int IntValue
{
get { return (int)GetValue(IntValueProperty); }
set { SetValue(IntValueProperty, value); }
}
private void OnIntValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
}
}
In this variant, the Register method accepts a property in the form of an expression, a default value of a specific type and not a static callback. The callback method accepts an instance of the generic of the DependencyPropertyChangedEventArgs class, where the old and new property values are cast to the property type. The recording itself was also simplified. Next, I will give the code of classes that allow applying such a record.
Custom generic code for DependecyProperty class:
public static class DependencyProperty where T : DependencyObject
{
public static DependencyProperty Register(Expression> propertyExpression)
{
return Register(propertyExpression, default(TProperty), null);
}
public static DependencyProperty Register(Expression> propertyExpression, TProperty defaultValue)
{
return Register(propertyExpression, defaultValue, null);
}
public static DependencyProperty Register(Expression> propertyExpression, Func> propertyChangedCallbackFunc)
{
return Register(propertyExpression, default(TProperty), propertyChangedCallbackFunc);
}
public static DependencyProperty Register(Expression> propertyExpression, TProperty defaultValue, Func> 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(Func> propertyChangedCallbackFunc)
{
if (propertyChangedCallbackFunc == null)
return null;
return new PropertyChangedCallback((d, e) =>
{
PropertyChangedCallback callback = propertyChangedCallbackFunc((T)d);
if (callback != null)
callback(new DependencyPropertyChangedEventArgs(e));
});
}
}
public delegate void PropertyChangedCallback(DependencyPropertyChangedEventArgs e);
This class as a generic parameter takes the type DependencyObject and contains several overloaded Register methods. The Register method takes its name from the property expression, converts the callback and creates the DependencyProperty using the standard method.
DependecyPropertyChangedEventArgs class code:
public class DependencyPropertyChangedEventArgs : 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; }
}
The code for the optional ExpressionExtensions class, which is used to get the property name by expression:
public static class ExpressionExtensions
{
public static string RetrieveMemberName(this Expression> 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");
}
}