WPF Tutorial - Introduction To Dependency Properties

Skill

WPF Tutorial - Introduction To Dependency Properties

Posted in:

So I was going to dive right in and do a part 2 on the WPF ListView tutorial from last week, but as I was writing the code I realized that a lot of it relies on some new and very different constructs that WPF provides to developers. Two of these are deep enough topics on their own that I thought it would be a good idea to give an introduction to them before I dove back into the ListView stuff. So today we are going to talk about Dependency Properties, and in a future tutorial I will talk about how binding works in WPF.

What are dependency properties? The quick definition from the MSDN docs says that a dependency property is a "property that is backed by the WPF property system." Not really a great one-line explanation, but really, I can't blame them. I can't come up with a good one line explanation either. But essentially, it gives you a bunch of infrastructure to do all the things that you often want to do with a normal property - validate it, coerce it into a proper range, give out change notifications, and a number of other aspects. We aren't going to touch on everything that a dependency property can do today - because there is a lot. But we will be talking about how to use them, how to create them, and how to set up validation/coercion/change notification.

So one of the first things I thought was weird about the definition of a dependency property is that it is a static. This didn't make sense to me at first - this property needs to store info relevant to a particular instance of a class, how is it going to do that if it is static? But then, as I read more about them, I realized that a dependency property definition was exactly that - a definition. You are essentially saying that class A will have a property B - and it makes sense that that definition would be static. The actual storage of a value for a dependency property is deep inside the WPF property system - you never have to worry about it.

One thing you do have to note, though, is that for a class to contain dependency properties, it has to in some way derive from DependencyObject. In deriving from this class, you get all the infrastructure needed to participate in the WPF dependency property system.

So at first glance, all the properties on the new WPF controls seem to be regular old properties. But don't be fooled - this is often just a simple wrapper around a dependency property (and the documentation will usually say this). So what does it mean for a property to be a simple wrapper around a dependency property? Lets look at the FrameworkElement.Height property as an example:

public double Height
{
  get
  {
    return (double)GetValue(HeightProperty);
  }
  set
  {
    SetValue(HeightProperty, value);
  }
}

Your first two questions are probably "What are these GetValue and SetValue functions?" and "What is this HeightProperty?", and those are very good questions indeed. Well, GetValue and SetValue are functions you get by deriving from DependencyObject. They allow you, as you might have guessed, to get and set the values of dependency properties. The HeightProperty is the dependency property itself - the static definition part of the FrameworkElement class.

So far, this is just added complexity, and you're wondering why there is yet another level of indirection on things. But here is where things get interesting. In the "old ways", to set up some sort of change notification on the Height property here, you would have had to override it in a new class deriving from this FrameworkElement class and added whatever you needed in the 'set' part of the property here. However, you no longer have to do things like that. Instead, you can:

FrameworkElement myElement;

//
// myElement gets set to an element
//

DependencyPropertyDescriptor dpd;
dpd = DependencyPropertyDescriptor.FromProperty(
    FrameworkElement.HeightProperty, typeof(FrameworkElement))

dpd.AddValueChanged(myElement, myHeightChangedFunction);

And now you have a function that will get called any time the height changes on myElement! I think that is pretty handy. You can detach the hook just as easily:

DependencyPropertyDescriptor dpd;
dpd = DependencyPropertyDescriptor.FromProperty(
    FrameworkElement.HeightProperty, typeof(FrameworkElement))

dpd.RemoveValueChanged(myElement, myHeightChangedFunction);

Ok, enough about other people's dependency properties - lets go make our own! Below we have a extremely simple class with a dependency property for "LastName", as well as a property wrapper around it:

public class Person : DependencyObject
{
  public static readonly DependencyProperty LastNameProperty =
      DependencyProperty.Register("LastName", typeof(string), typeof(Person));

  public string LastName
  {
    get
    {
      return (string)GetValue(LastNameProperty);
    }
    set
    {
      SetValue(LastNameProperty, value);
    }
  }
}

The Register call is pretty simple - you give the property a name (in this case "LastName"), you say what type of information the property will hold (in this case a string), and you say what type of object this property is attached to (in this case Person). A little verbose, especially when you add in the property wrapper, but not too bad. And I'm sure Microsoft will find a way to streamline the syntax in the next version of C#.

A little bit of a side note here - don't ever put anything but the GetValue and SetValue calls inside the property wrapper. This is because you never know if someone will set the property through the wrapper, or straight through a SetValue call - so you don't want to put any extra logic in the property wrapper. For example, when you set the value of a dependency property in XAML, it will not use the property wrapper - it will hit the SetValue call directly, bypassing anything that you happened to put in the property wrapper.

Back to creating dependency properties. The constructor for Register has a couple more optional arguments. And we are going to jump right in and use them all!

public class Person : DependencyObject
{
  public static readonly DependencyProperty LastNameProperty =
      DependencyProperty.Register("LastName", typeof(string), typeof(Person),
      new PropertyMetadata("No Name", LastNameChangedCallback, LastNameCoerceCallback),
      LastNameValidateCallback);

  private static void LastNameChangedCallback(
      DependencyObject obj, DependencyPropertyChangedEventArgs e)
  {
    Console.WriteLine(e.OldValue + " " + e.NewValue);
  }
 
  private static object LastNameCoerceCallback(DependencyObject obj, object o)
  {
    string s = o as string;
    if (s.Length > 8)
      s = s.Substring(0, 8);
    return s;
  }

  private static bool LastNameValidateCallback(object value)
  {
    return value != null;
  }

  public string LastName
  {
    get
    {
      return (string)GetValue(LastNameProperty);
    }
    set
    {
      SetValue(LastNameProperty, value);
    }
  }
}

The two other arguments to Register are a PropertyMetadata instance and a validateValueCallback. There are a couple different classes that derive from PropertyMetadata, but today we are just using the base one. The PropertyMetadata instance allows us to set a default value (in this case "No Name"), a property changed callback (LastNameChangedCallback), and a coerce value callback (LastNameCoerceCallback). The default value does exactly what you might expect. The change callback can do whatever you want it to do, and you have plenty of information with which to do it. The DependencyPropertyChangedEventArgs contain both the old and the new values of the property, as well as a reference to what property was changed. And the DependencyObject passed in is the object on which the property was changed. This is needed because, as you can see, this method is static. So every time the "LastName" property is changed on any object, this function will get called.

The same goes for the coerce callback - we get the object on which the property is being changed, and the possible new value. Here we get the opportunity to change the value - in this case, apparently last names are forced to be 8 characters or less.

And finally, we have the LastNameValidateCallback. This just gets the possible new value, and returns true or false. If it returns false, an exception is blown - so in this case, if anyone ever tries to set a null last name, they better watch out.

So there you go, the basics on dependency property usage and creation. There are a couple areas I haven't covered - inheritance, attached properties, and overriding metadata to name a few. But hopefully this gets off on the right foot with respect to dependency properties, and I'll probably write a tutorial in the next few weeks on those other areas. As always, questions and comments are welcome.

Quang Tran Minh
03/18/2008 - 03:48

Thanks a lot man, an extremly good explanation on this topic!!

Greetings,
Quang Tran Minh.

reply

Mathias
03/26/2008 - 11:52

Yes indeed, a perfect first step into the topic - I finally got the idea;)

Many thanks! Cheers
Matze

reply

Vidhya
04/10/2008 - 00:14

I am looking at more information on Placement property.

Two layouts may be used
- 3 textboxes in a line
- 1 textbox in one line ,
next line last two textboxes

How do I go abt this? Any example with AffectArrange would be greatful

reply

Momin
05/16/2008 - 09:43

Thanks for a great explanation. I was also equally confused when I saw that the dependency property was defined static. I am still curious if you know where I can find info on how the DP's work internally?

reply

Vincent
06/03/2008 - 13:38

Why is the DependencyObject readonly? What's its significance?

reply

Yasser Azeem
10/09/2008 - 00:01

good article !!!

reply

Lito
10/18/2008 - 19:28

You guys have a knack for explaining stuff. I'm grateful. I encountered your site when I googled for wpf listview and found your listview tutorial part 1. That was the best introductory article I've read about listview. Naturally, I kept searching for part 2, which I never found. Now that you've done DP, can you work on LV part 2? Thanks.

reply

The Tallest
10/25/2008 - 00:52

To be honest, I kind of got distracted and never got around to writing part 2, but I'll put it back in my queue of tutorials to write. Is there anything in particular you would like me to address? I was thinking about delving into the concepts of sorting and filtering.

reply

Jim H
12/04/2008 - 21:45

I am doing this same thing in VB.NET and it does not allow me to declare the dependency object as STATIC.(I get a squigly underline and an error:
"Static is not valid on a member variable declaration."
My code:
Public Static ReadOnly LastNameProperty As DependencyProperty = DependencyProperty.Register("LastName", GetType(String), GetType(cPerson))

Did I miss something?
JH

reply

The Tallest
12/07/2008 - 14:26

I don't know much about Visual Basic, but I think you are supposed to use the keyword shared instead of the keyword static. I also don't know if readonly is a valid keyword in VB, so you might have to remove that too.

reply

Ray
01/06/2009 - 20:17

Great basic explanation, it defiantly cleared up a few things for me. Thanks

:)

reply

Abdul Sami
01/08/2009 - 14:24

Good article. I got to know the clear picture of Dependency property. I was breaking my head to understand the concept. This was really helpfull.

reply

Andreas Symeonides
02/24/2009 - 19:51

Very well explained. Thanks a lot

reply

raghu
03/31/2009 - 00:49

give brief explanation
becuse iam a beginner

reply

Hugo Olivera
05/21/2009 - 22:06

Very clear considering the complexity...thanks

reply

nm4568
07/16/2009 - 20:17

I've read a couple tutorials on this trying to wrap my head around this topic. Up until this one, it has been frustrating. I can't wait until your next blog.

Many Thanks!!!

reply

Avi Farah
07/29/2009 - 05:44

Superb
Many thanks
--Avi

reply

Gk
08/07/2009 - 11:50

Great Explanation for entry level WPF.
Could you please provide some insight into Attached property too.

reply

Venkata Reddy
10/07/2009 - 09:09

I am looking for the benefits of using dependency properties
over normal .Net properties.
From my point of view
Notification is the advantage.

Thanks

reply

Daniel
10/13/2009 - 06:15

Great, perfect, super-clear... why in the last years MSDN has become so...... difficult ? ....so pragmatic ? Win32_SDK was so clear... Thanks

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.

Sponsors