您的位置:首页 > 运维架构

Introduction To Dependency Properties of WPF

2010-08-26 16:29 387 查看

http://www.switchonthecode.com/tutorials/wpf-tutorial-introduction-to-dependency-properties
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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: