Introduction
Almost all Microsoft UI frameworks use the same architectural model based on DependencyObjects
and DependencyProperties
. The same concept is used in WPF and UWP, outdated frameworks like Silverlight and Windows Phone, even in Xamarin and other curiosities like CSHTML5 (http://cshtml5.com/). DependencyObject
is a base class that represents an object participating in dependency property system. It is implemented by UI controls, geometric shapes, templates, styles, etc. DependencyProperty
(BindableProperty
in Xamarin) is a “backing store” for a property which enables animation, styling, data binding, etc.
The fastest way to add a dependency property is to use a code snippet. Just type “propdp
” and press tab and the following code will appear:
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int),
typeof(ownerclass), new PropertyMetadata(0));
The problems of this implementation are visible at first glance. First of all, the property name is hardcoded as string
. This problem ceased to exist with the introduction of the nameof
operator. Today, the above code should look like this:
DependencyProperty.Register(nameof(MyProperty),
typeof(int), typeof(ownerclass), new PropertyMetadata(0));
From now on, the tricks related to extracting property names from lambdas are no longer needed. These days, almost no one will raise a PropertyChanged
event this way:
RaisePropertyChanged(() => MyProperty);
It can be done this way:
RaisePropertyChanged(nameof(MyProperty));
Or this way (thanks to CallerMemberNameAttribute
):
RaisePropertyChanged();
The main problem in registering dependency properties however, is the danger of type mismatch. What if I replace int
with double
in the above example? The code looks correct but it will throw a runtime exception because the default value specified in PropertyMetadata
is an integer:
new PropertyMetadata(0));
To avoid the exception, we will have to write it this way:
new PropertyMetadata(0d));
This problem has been already pointed out in this deleted article which in my opinion, has obtained an unfair assessment. People in comments section are mainly arguing against the title of the article and they pay attention to the fact that the runtime exception is thrown which tells exactly what is the problem - in my opinion, this results from a clear misunderstanding of the purpose of strong typing which is the point of the article.
The solution that I wanted to present here is very similar to the one proposed in Xamarin some time ago:
public static readonly BindableProperty SomePropertyProp = BindableProperty.Create<CustomControl,
int>(c => c. SomeProperty, 1);
public int SomeProperty
{
get { return (int)GetValue(SomePropertyProp); }
set { SetValue(SomePropertyProp, value); }
}
For unclear reasons, Xamarin withdrew this implementation just after the introduction of the nameof
operator. As I mentioned before, the nameof
operator removes the problem related to hardcoded property name but the problem related to type safety still exists. If you know the reason for this change, please write it in the comments section.
In this article, I am going to present a similar implementation for WPF and UWP which is used in open source music notation control library Manufaktura.Controls
(which was previously described in this article) and a code snippet that makes it easier to use.
How to Use
First of all, you can import a code snippet attached to Manufaktura.Controls.WPF
library. It can be found in Snippets folder. To import a snippet in Visual Studio, go to Tools\Code Snippet Manager…\Import… and pick the .snippet file attached to the project. Now you can type “propdpx
” and press tab and the following code will appear:
public static readonly DependencyProperty MyPropertyProperty =
DependencyPropertyEx.Register<MyDepObject, string>(v => v.MyProperty, default(string),
(depObject, oldValue, newValue) => { });
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
This code is similar to the one used with Dependency.Register
but the type safety is provided by generic type argument. Name safety is provided by lambda (it could be replaced with nameof
these days). There is also a default property changed handler:
(depObject, oldValue, newValue) => {
});
It’s better than old property changed handler because each parameter (dependency object, old value and new value) has the proper type.
How Does It Work
The implementation is simple. It depends on calling DependencyProperty.Register
and casting callback arguments to proper types:
public static class DependencyPropertyEx
{
public delegate void PropertyChangedCallback<TControl, TProperty>
(TControl control, TProperty oldValue, TProperty newValue)
where TControl : DependencyObject;
public static DependencyProperty Register<TControl, TProperty>
(Expression<Func<TControl, TProperty>> property, TProperty defaultValue,
PropertyChangedCallback<TControl, TProperty> propertyChangedCallback = null)
where TControl : DependencyObject
{
if (propertyChangedCallback == null)
return DependencyProperty.Register(GetPropertyName(property), typeof(TProperty),
typeof(TControl), new PropertyMetadata(defaultValue));
return DependencyProperty.Register(GetPropertyName(property), typeof(TProperty),
typeof(TControl), new PropertyMetadata(defaultValue, (obj, args) =>
{
propertyChangedCallback(obj as TControl, (TProperty)args.OldValue,
(TProperty)args.NewValue);
}));
}
private static string GetPropertyName<TControl, TProperty>
(Expression<Func<TControl, TProperty>> propertyLambda)
{
var memberExpression = propertyLambda.Body as MemberExpression;
if (memberExpression == null)
throw new Exception("Lambda expression should be
in the following format: control => control.Property.");
return memberExpression.Member.Name;
}
}
Code Snippet
In order to make inserting dependency properties easier, I created the following snippet:
="1.0"="utf-8"
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Typed dependency property</Title>
<Author>Manufaktura programów Jacek Salamon</Author>
<Description>Inserts strongly typed dependency property registration</Description>
<Shortcut>propdpx</Shortcut>
</Header>
<Snippet>
<Imports>
<Import>
<Namespace>System.Windows</Namespace>
</Import>
<Import>
<Namespace>Manufaktura.Controls.WPF.Bindings</Namespace>
</Import>
</Imports>
<Declarations>
<Literal>
<ID>PropertyName</ID>
<ToolTip>Define property name.</ToolTip>
<Default>MyProperty</Default>
</Literal>
<Literal>
<ID>PropertyType</ID>
<ToolTip>Define property type.</ToolTip>
<Default>string</Default>
</Literal>
<Literal>
<ID>PropertyTarget</ID>
<ToolTip>Define property target.</ToolTip>
<Default>MyDepObject</Default>
</Literal>
</Declarations>
<Code Language="CSharp">
<![CDATA[
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
Process of creating code snippets in Visual Studio is covered in this article.
Other Interesting Features in Manufaktura.Controls
ResetableLazy
ResetableLazy
is just a façade around Lazy
class that allows you to reinitialize Lazy
object with Reset
method:
public class ResetableLazy<T>
{
private readonly Func<T> valueFactory;
private Lazy<T> innerLazy;
public ResetableLazy(Func<T> valueFactory)
{
this.valueFactory = valueFactory;
innerLazy = new Lazy<T>(valueFactory);
}
public bool IsValueCreated => innerLazy.IsValueCreated;
public T Value => innerLazy.Value;
public void Reset()
{
lock (innerLazy)
{
innerLazy = new Lazy<T>(valueFactory);
}
}
public override string ToString() => innerLazy.ToString();
public void Touch()
{
var x = Value;
}
}
There is also a useful Touch
method which enables you to initialize value without returning it. It should improve the clarity of code.
Formula Bindings
FormulaBinding
is a new way of databinding in XAML frameworks that brings bindings to the simplicity and flexibility of JavaScript frameworks. It is described in this article.