This strict precedence is
required to avoid a lot of fighting and squabbles among styles and
animations and everything else. It would be chaos otherwise, and that
violates our fundamental desire that code be completely deterministic.
What Silverlight providesis
an infrastructure to manage all the different ways properties can be set
and to impose some kind of order. Dependency properties are a major part of this infrastructure. They’re called dependency properties because the properties depend on a bunch of different external forces, which are then mediated.
Dependency properties are built on top of existing .NET
properties, and there’s some grunt work involved, and some extra
typing, but you’ll be coding dependency properties automatically before
you know it.
Among other things,
dependency properties provide the property-setting precedence. It occurs
way under the covers and it’s not something you can mess with.
Dependency properties also provide a very structured way to give
properties a default value, and to provide callback methods that are
invoked when the value of the property changes.
Almost all the properties of the Silverlight classes
encountered so far have actually been dependency properties. It’s
easier listing the exceptions to this rule! Two that come to mind are
the Children property of Panel and the Text property of Run.
Any class that implements dependency properties must derive from DependencyObject, which is a very basic class in the Silverlight class hierarchy. Many classes in Silverlight derive from DependencyObject, including the big one: UIElement. That means Button derives from DependencyObject, which of course means that any class that derives from Button can implement dependency properties.
A BetterGradientButton class with dependency properties starts off normally:
public class BetterGradientButton : Button
{
}
As with NaiveGradientButton, BetterGradientButton defines two properties named Color1 and Color2. A dependency property begins with a public field of type DependencyProperty that has the same name as the property but with the word Property appended. So in the BetterGradientButton class, the first step to defining a Color1 property is to define a public field of type DependencyProperty named Color1Property.
public class BetterGradientButton : Button
{
public static readonly DependencyProperty Color1Property =
DependencyProperty.Register("Color1",
typeof(Color),
typeof(BetterGradientButton),
new PropertyMetadata(Colors.Black, OnColorChanged));
}
Not only is it a field, but it’s a public static field, and it’s customarily defined as readonly as well, which means it can’t be changed after it’s defined. Once a DependencyProperty is created for a particular class, it doesn’t change, and it’s shared among all instances of that class.
Generally you create an object of type DependencyProperty by calling the static DependencyProperty.Register
method. (The only exception is for attached properties.) The first
argument is a text string of the property name; the second argument is
the type of the property, in this case Color; the third argument is the class defining the property, in this case BetterGradientButton.
The final argument is an object of type PropertyMetadata, and there are only two possible pieces of information you supply in the PropertyMetadata constructor. One is the default value of the property—the value of the property if it’s not otherwise assigned. If you don’t supply this default value for a reference type, it will be assumed to be null. For a value type, it’s the type’s default.
I’ve decided that the Color1 property should have a default value of Colors.Black.
The second part of the PropertyMetadata
constructor is the name of a handler that is called when the property
changes. This handler is only called if the property really changes. For
example, if the property has a default value of Colors.Black, and the property is then set to a value of Colors.Black, the property changed handler will not be called.
I know it seems weird for something called a dependency property with a type of DependencyProperty and a name of Color1Property to be defined as a field, but there it is.
It’s easy to confuse the two classes DependencyObject and DependencyProperty. Any class that has dependency properties must descend from DependencyObject, just as normal classes descend from Object. The class then creates objects of type DependencyProperty just as a normal class might define regular properties.
It’s not necessary to define the entire DependencyProperty in the static field. Some programmers prefer instead to initialize the DependencyProperty field in the static constructor:
public class BetterGradientButton : Button
{
public static readonly DependencyProperty Color1Property;
static BetterGradientButton()
{
Color1Property = DependencyProperty.Register("Color1",
typeof(Color),
typeof(BetterGradientButton),
new PropertyMetadata(Colors.Black, OnColorChanged));
}
}
There’s really no difference between these two techniques.
Besides the static field of type DependencyProperty you need a regular .NET property definition for the Color1 property:
public class BetterGradientButton : Button
{
public static readonly DependencyProperty Color1Property =
DependencyProperty.Register("Color1",
typeof(Color),
typeof(BetterGradientButton),
new PropertyMetadata(Colors.Black, OnColorChanged));
public Color Color1
{
set { SetValue(Color1Property, value); }
get { return (Color)GetValue(Color1Property); }
}
}
This definition of the Color1 property is standard. The set accessor calls SetValue referencing the Color1Property dependency property, and the get accessor calls GetValue also referencing Color1Property.
Where did these two methods SetValue and GetValue come from? SetValue and GetValue are two public methods defined by DependencyObject and inherited by all derived classes. Notice that the second argument to SetValue is the value to which the property is being set. The return value of GetValue is of type object so it must be explicitly cast to a Color.
In connection with dependency properties, the Color1 property definition is said to be the definition of the CLR property—the .NET Common Language Runtime property—to distinguish it from the DependencyProperty object defined as a public static field. It is sometimes said that the CLR property named Color1 is “backed by” the dependency property named Color1Property.
That terminology is convenient when you want to distinguish the
property definition from the definition of the public static field. But
just as often, both pieces—the public static field and the property
definition—are collectively referred to as “the dependency property” or (if you’re really cool) “the DP.”
It is very important that your CLR property does nothing more than call SetValue and GetValue.
This is not the place for any kind of validity checking or
property-changed processing. The reason is that you never really know
how a dependency property is being set. You might think the property is
always set like this:
btn.Color1 = Colors.Red;
However, the SetValue and GetValue methods defined by DependencyObject are public, so some code could just as easily set the property like this:
btn.SetValue(GradientButton2.Color1Property, Colors.Red);
Or, the property could be set in a way that is known only to the Silverlight internals.
On the other hand, don’t mistakenly omit the CLR property. Sometimes if you just define the DependencyProperty field and forget the CLR property, some things will work but others will not.
Here’s the class with a DependencyProperty and CLR property for Color2 as well:
public class BetterGradientButton : Button
{
public static readonly DependencyProperty Color1Property =
DependencyProperty.Register("Color1",
typeof(Color),
typeof(BetterGradientButton),
new PropertyMetadata(Colors.Black, OnColorChanged));
public static readonly DependencyProperty Color2Property =
DependencyProperty.Register("Color2",
typeof(Color),
typeof(BetterGradientButton),
new PropertyMetadata(Colors.White, OnColorChanged));
public Color Color1
{
set { SetValue(Color1Property, value); }
get { return (Color)GetValue(Color1Property); }
}
public Color Color2
{
set { SetValue(Color2Property, value); }
get { return (Color)GetValue(Color2Property); }
}
}
In the DependencyProperty definition for Color2, I set the default value to Colors.White.
Both DependencyProperty fields refer to a property-changed handler named OnColorChanged. Because this method is referred to in the definition of a static field, the method itself must be static and here’s what it look like:
static void OnColorChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
. . .
}
This is a static method so it’s the same method for all instances of BetterGradientButton, and that’s a little problem. Normally in a static method you can’t access any non-static properties or methods, so at first you might assume that this method can’t refer to anything involving a particular instance of BetterGradientButton.
But notice that the first argument to this property-changed handler is of type DependencyObject. This argument is actually the particular instance of BetterGradientButton whose property is being changed. This means that you can safely cast this first argument to an object of type BetterGradientButton:
static void OnColorChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
BetterGradientButton btn = obj as BetterGradientButton;
. . .
}
You can then use that btn variable to access all the instance properties and instance methods in the class.
The second argument to the
handler gives you specific information on the particular property that’s
being changed, and the old and new values of that property.
Here’s the complete BetterGradientButton class:
Example 1. SilverlightProject: BetterGradientButtonDemo File: BetterGradientButton.cs (excerpt)
public class BetterGradientButton : Button { GradientStop gradientStop1, gradientStop2;
public static readonly DependencyProperty Color1Property = DependencyProperty.Register("Color1", typeof(Color), typeof(BetterGradientButton), new PropertyMetadata(Colors.Black, OnColorChanged));
public static readonly DependencyProperty Color2Property = DependencyProperty.Register("Color2", typeof(Color), typeof(BetterGradientButton), new PropertyMetadata(Colors.White, OnColorChanged));
public BetterGradientButton() { LinearGradientBrush brush = new LinearGradientBrush(); brush.StartPoint = new Point(0, 0); brush.EndPoint = new Point(1, 0);
gradientStop1 = new GradientStop(); gradientStop1.Offset = 0; gradientStop1.Color = Color1; brush.GradientStops.Add(gradientStop1);
gradientStop2 = new GradientStop(); gradientStop2.Offset = 1; gradientStop2.Color = Color2; brush.GradientStops.Add(gradientStop2);
Foreground = brush; }
public Color Color1 { set { SetValue(Color1Property, value); } get { return (Color)GetValue(Color1Property); } }
public Color Color2 { set { SetValue(Color2Property, value); } get { return (Color)GetValue(Color2Property); } }
static void OnColorChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { BetterGradientButton btn = obj as BetterGradientButton;
if (args.Property == Color1Property) btn.gradientStop1.Color = (Color)args.NewValue;
if (args.Property == Color2Property) btn.gradientStop2.Color = (Color)args.NewValue; } }
|
Like the earlier NaiveGradientButton class, the class has two private instance fields of type gradientStop1 and gradientStop2. The constructor is also quite similar to the earlier version, but this one has a significant difference: The Color property of each GradientStop object is initialized from the Color1 and Color2 properties:
gradientStop1.Color = Color1;
gradientStop2.Color = Color2;
Accessing those Color1 and Color2 properties causes calls to GetValue with the Color1Property and Color2Property arguments. GetValue returns the default values defined in the DependencyProperty field: Colors.Black and Colors.White. That’s how the LinearGradientBrush is created with the default colors.
There are numerous ways to code the property-changed handler down at the bottom of the class. In the BetterGradientButton class, I’ve made use of two properties in DependencyPropertyChangedEventArgs: The property named Property of type DependencyProperty indicates the particular dependency property being changed. This is very handy if you’re sharing a property-changed handler among multiple properties as I am here.
DependencyPropertyChangedEventArgs also defines OldValue and NewValue
properties. These two values will always be different. The
property-changed handler isn’t called unless the property is actually
changing.
By the time the
property-changed handler has been called, the property has already been
changed, so the handler can be implemented by accessing those properties
directly. Here’s a simple alternative:
static void OnColorChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
BetterGradientButton btn = obj as BetterGradientButton;
btn.gradientStop1.Color = btn.Color1;
btn.gradientStop2.Color = btn.Color2;
}
This version doesn’t check which property is changing, so for any particular call to OnColorChanged, one of those two statements is superfluous. You’ll be comforted to know that GradientStop derives from DependencyObject, and the Color property is a dependency property, so the property-changed handler in GradientStop doesn’t get called if one of the properties is not actually changing.
Here’s something I do quite often, and I’m a much happier programmer as a result:
Rather than warp my brain by using a reference to a particular instance
of a class within a static method, I use the static method for the sole
purpose of calling an instance method with the same name. Here’s how my
technique would look in BetterGradientBrush:
static void OnColorChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
(obj as BetterGradientButton).OnColorChanged(args);
}
void OnColorChanged(DependencyPropertyChangedEventArgs args)
{
if (args.Property == Color1Property)
gradientStop1.Color = (Color)args.NewValue;
if (args.Property == Color2Property)
gradientStop2.Color = (Color)args.NewValue;
}
This instance method can do everything the static method can do but without the hassle of carrying around a reference to a particular instance of the class.
Let’s see this new class in
action. It would be a shame if all this hard work didn’t improve things.
As in the earlier program, the Resources collection in MainPage.xaml has a Style element targeting the custom button. But now the Setter tags for Color1 and Color2 have been optimistically uncommented:
Example 2. SilverlightProject: BetterGradientButtonDemo File: MainPage.xaml (excerpt)
<phone:PhoneApplicationPage.Resources> <Style x:Key="gradientButtonStyle" TargetType="local:BetterGradientButton"> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="Color1" Value="Cyan" /> <Setter Property="Color2" Value="Pink" /> </Style> </phone:PhoneApplicationPage.Resources>
|
The content area is basically the same:
Example 3. SilverlightProject: BetterGradientButtonDemo File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel> <local:BetterGradientButton Content="Better Gradient Button #1" HorizontalAlignment="Center" />
<local:BetterGradientButton Content="Better Gradient Button #2" Color1="Blue" Color2="Red" HorizontalAlignment="Center" />
<local:BetterGradientButton Content="Better Gradient Button #3" Color1="{StaticResource PhoneForegroundColor}" Color2="{StaticResource PhoneBackgroundColor}" HorizontalAlignment="Center" />
<local:BetterGradientButton Content="Better Gradient Button #4" Style="{StaticResource gradientButtonStyle}" /> </StackPanel> </Grid>
|
The really great news is in the screen shot:
The first button shows the effect of the defaults, a concept built into dependency properties, and the last button shows that the Style works.
A few miscellaneous notes:
Notice that you don’t have direct access to the actual values of the dependency properties. They are obviously stored somewhere private that is accessible only through SetValue and GetValue. Presumably DependencyObject maintains a dictionary to store a collection of dependency properties and their values. It must be a dictionary because it’s possible to use SetValue to store certain types of dependency properties—specifically, attached properties—in any DependencyObject.
Watch out for the first argument to the PropertyMetadata constructor. It is defined as type object. Suppose you’re creating a DependencyProperty for a property of type double and you want to set a default value of 10:
public static readonly DependencyProperty MyDoubleProperty =
DependencyProperty.Register("MyDouble",
typeof(double),
typeof(SomeClass),
new PropertyMetadata(10, OnMyDoubleChanged));
The C# compiler will interpret that value of 10 as an int, and generate code to pass an integer value of 10 to the PropertyMetadata constructor, which will try to store an integer value for a dependency property of type double. That’s a runtime error. Make the data type explicit:
public static readonly DependencyProperty MyDoubleProperty =
DependencyProperty.Register("MyDouble",
typeof(double),
typeof(SomeClass),
new PropertyMetadata(10.0, OnMyDoubleChanged));
You might have cause to create a read-only dependency property. (For example, the ActualWidth and ActualHeight properties defined by FrameworkElement have get accessors only.) At first, it seems easy:
public double MyDouble
{
private set { SetValue(MyDoubleProperty, value); }
get { return (double)GetValue(MyDoubleProperty); }
}
Now only the class itself can set the property.
But wait! As I mentioned earlier, the SetValue method is public, so any class can call SetValue
to set the value of this property. To protect a read-only dependency
property from unauthorized access you’ll need to raise an exception if
the property is being set from code external to the class. The easiest
logic probably entails setting a private flag when you set the property
from within the class and then checking for that private flag in the property-changed handler.
You can easily tell from the
documentation if a particular property of an existing class is backed by
a dependency property. Just look in the Fields section for a static
field of type DependencyProperty with the same name as the property but with the word Property attached.
The existence of the static DependencyProperty
field allows code or markup to refer to a particular property defined
by a class independent of any instance of that class, even if an
instance of that class has not yet been created. Some methods—for
example, the SetBinding method defined by FrameworkElement—have arguments that allow you to refer to a particular property, and the dependency property is ideal for this.
Finally, don’t feel obligated to make every property in your classes a dependency
property. If a particular property will never be the target of a style,
or a data binding, or an animation, there’s no problem if you just make
it a regular property.
For example, if you plan to use multiple RadioButton controls to let the user select an object of type Color, you could derive from RadioButton and define a property for associating a Color object with each RadioButton:
public class ColorRadioButton : RadioButton
{
public Color ColorTag { set; get; }
}
You could then set that property in XAML and reference it later in code for easily determining what Color each RadioButton represents. You don’t need a dependency property for a simple application like this.