Lesson 33 - DependencyProperties in C# .NET WPF
In first WPF lessons, we've learned about principles
of data binding and the INotifyPropertyChanged
interface. We
know that these are powerful tools and are even necessary to build more robust
applications. Otherwise, it'd be very difficult to manually make sure form data
are up-to-date unlike data in an application.
In addition to INotifyPropertyChanged
, WPF has another tool that
can also automatically respond to value changes of a property. This tool is
called Dependency Properties.
Dependency Properties
WPF went for it from scratch and came up with an innovation of C# properties themselves. The concept of Dependency Properties is much more complex and powerful.
All WPF controls are built on Dependency Properties internally.
That's so they can support data binding even in the opposite direction. This way
it's internally possible that a TextBlock
changes its
Text
, which is a DependencyProperty
and not a common
CLR
property, as we've probably thought so far.
Until we create our own WPF control, Dependency Properties don't really bring much new to us, and its syntax can be confusing for other programmers. The usage of Dependency Properties in code we write and that isn't our own WPF element, e.g. in ViewModels, is a relatively controversial topic and there are long discussions about it. As the result, using Dependency Properties it can be a bit faster and it allows you to animate values, for example. In today's lesson we'll introduce this technology mainly because WPF is built on it internally and it's therefore a basic knowledge. You can also come across them in other source code. But that doesn't mean you have to start using them everywhere, instead of how we've programmed so far.
A classic property vs. DependencyProperty
Let's compare known implementations of properties and then show the new one.
A classic property
Everything is fine here:
public class User { public int Age { get; set; } }
A classic property for data binding
For example, if we wanted to bind a form to a property, we'd have to
implement the INotifyPropertyChanged
interface in the class (or
INotifyCollectionChanged
if the class represented a collection).
Only then did the control, which was bound to the property, find out that its
value had changed. Here we need to store the data in some private attribute of
the class (typically with the same name as the property, but with a lowercase
letter or underscore):
public class User: INotifyPropertyChanged { private int age; public int Age { get => age; set { OnPropertyChanged(nameof(Age)); } } private void OnPropertyChanged(string property) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); }
An internal attribute is a backing field of its property. .[hint]
In the setter, we call the recovery method, which when the property changes, invokes an appropriate event, to which other parts of the application can automatically respond. Still nothing new.
DependencyProperty
To store a value of a DependencyProperty
we no longer need a
common attribute of the given type, but we'll find it out dynamically.
Therefore, a value doesn't have to be stored at all and some default one
can be used, which reduces memory usage of the application. It's no longer the
case that each property has one stored value. WPF has found out that
after all we don't change a font, color, shadow, etc. of controls... So why
should each button store a value in its attribute, when we can simply return the
same default value in the getter of all buttons?
We use a static instance of the
DependencyProperty
class to get a value. We then find the value by
calling the GetValue()
method of the DependencyObject
class, from which each class with a DependencyProperty must
inherit. And this dependency is one of the reasons why we shouldn't use
DependencyProperties in ViewModels. Whereas UserControl
, an
ancestor of its WPF elements, already has the DependencyObject
class inherited. We use the analog method SetValue()
to set a
value.
Let's try it. We'll add a new UserControl
and we'll create a
City
property in its Code Behind:
public static readonly DependencyProperty CityProperty = DependencyProperty.Register("City", typeof(string), typeof(ClassType)); public string City { get { return (string)GetValue(CityProperty); } set { SetValue(CityProperty, value); } }
First we added a static attribute of the DependencyProperty
type, initialized by the static Register()
method. The name
DependencyProperty
should always end with the word
Property
, it's the naming convention in WPF.
Therefore, the property's backing field is here a
static DependencyProperty
instance and this way are default values
shared across instances.
In order for a value to be accessible as a normal .NET property, we must add
not only a DependencyProperty
instance, but a classic property too.
It does nothing but gets and sets values from its
DependencyProperty
using the GetValue()
and
SetValue()
methods inherited from
DependencyObject
.
Important: We don't add any logic to these properties, as
they're called only when we set a property from code. If we set a property from
XAML, the SetValue()
method is called directly.
We replace the ClassType
type with our class type or the type of
a class to which the property logically belongs.
Such a property doesn't need help in the form of a manual implementation of
INotifyPropertyChanged
, even if its declaration is similarly
complex.
Note that the CityProperty
attribute is really
static. When we set the value of the property implemented as
DependencyProperty
, it won't be stored in the instance attribute of
our instance, but in the dictionary of keys and values provided by the
DependencyObject
class. The key of the item is the name of the
property and the value is then the value we want to set.
Benefits of DependencyProperties
There are several benefits of this new property type.
Lower memory requirements
We've already mentioned that we don't have to store all values of each control, but only those that have different values from default values. E.g. a row of buttons where the last one is blue will therefore be stored in the memory as:
- default properties, including a color, which will be saved only once and used by all buttons
- a blue color for the last button
Inheritance of values
If no local value is set, a DependencyProperty
traverses the
ancestor tree up until it finds a value, which a property then returns it.
Change notification
As we've already said, perhaps the most tempting reason to consider Dependency Properties is the built-in mechanism for notifying value changes, e.g. to restore a property print somewhere on a form without having to call it manually. By registering a callback in the property metadata, you receive a notification when a property's value changes. So it works on both sides.
In the next lesson, Our own control with DependencyProperties in C# .NET WPF , we'll program an example of using DependencyProperties in WPF in our own UserControl.
Comments
No one has commented yet - be the first!