Udostępnij za pośrednictwem


Why dependency properties need a wrapper

Dependency properties are not a new thing, they have been part of WPF and Silverlight for years. They enable key features such as data binding, styles, animations, change notifications and default values.

Their behavior however can differ slightly from API to API, WPF and Silverlight for example, have slightly different behaviors when it comes to dependency properties. This blog post will look in the details of how dependency properties work in Windows Store Apps.

Implementing a dependency property

The first thing to know about dependency properties is that the infrastructure that make them work is in the DependencyObject class. Therefore, you will need to inherit from DependencyObject if you want to use dependency properties.

Declaring the dependency property

Declaring a dependency property is done through a static method. This will return a key (of type DependencyProperty) that you will be able to pass to GetValue and SetValue to get and set the property on a specific DependencyObject. Here is how to declare the dependency property:

 public static readonly DependencyProperty IsSpinningProperty = 
    DependencyProperty.Register(
        "IsSpinning",
        typeof(bool),
        typeof(SpinningControl),
        new PropertyMetadata(false)
    );

Declaring an attached dependency property is very similar:

 public static readonly DependencyProperty IsMovableProperty =
    DependencyProperty.RegisterAttached(
        "IsMovable",
        typeof(bool),
        typeof(ObjectContainer),
        new PropertyMetadata(false)
    );

In both snippets, I have highlighted in red the string that will identify the property within the dependency property infrastructure. This name will be used for example when you want to animate the property from XAML.

Implementing the wrapper

A wrapper for GetValue and SetValue is usually defined along with the dependency property. There are some specific conventions on how they should be declared. Here is how to implement them:

 public bool IsSpinning
{
    get { return (bool)GetValue(IsSpinningProperty); }
    set { SetValue(IsSpinningProperty, value); }
}

And for a dependency property, the wrapper should be as follow:

 public static void SetIsMovable(DependencyObject dependencyObject, bool value)
{
    dependencyObject.SetValue(IsMovableProperty, value);
}

public static bool GetIsMovable(DependencyObject dependencyObject)
{
    return (bool)dependencyObject.GetValue(IsMovableProperty);
}

I have highlighted in red the name of the property that will be looked up in some occasions (see below) when using the property from XAML.

When is the wrapper used?

Dependency property wrappers are not just wrappers. If you don't implement them, you won't be able to use the dependency property from XAML. When setting a dependency property from XAML, the compiler will check that the dependency property you are using actually exists. It can only do so by performing a static analysis of the code. As a result, it is unable to know that you declared a property called "IsSpinning" by calling DependencyProperty.Register. What the compiler does instead is check the existence (and type) of the wrapper. If you use a non-attached dependency property, it will check for the property wrapper, and if you use an attached property, it will check for the GetX and SetX methods (as shown above). This check is also done for example when you declare a Setter in a Style, targeting a specific property. Note however that the compiler doesn't check what happens inside that wrapper. It could be empty, or do something completely different, but as long as it exists and the type matches, compilation will succeed.

When setting a property in XAML to a static value, Windows will actually call the wrapper. That means that it will work even if it is a standard .NET property that is not backed by a dependency property. The SetX method is called if you use the syntax of an attached dependency property. However if you try anything such as setting the value to a binding, or using the property in a style or an animation, Windows will no longer use the wrapper, and will call directly GetValue or SetValue, matching the name of the property that you are using in XAML with the name you declared when you called DependencyProperty.Register. If that name doesn't exist, you will get a runtime error. That's why the name in the wrapper and the name in DependencyProperty.Register (all in red above) have to match. If they don't match, you will either get a compile time or runtime error.

Here are the key takeaways about how dependency properties work when used from XAML:

  1. Compile time
    • Compile time check is always performed by looking at the name and type of the wrapper.
  2. Runtime
    • Setting the property to a static value in XAML will cause the wrapper to be called (looked up by its name). That works even if there is no dependency property registered to that name.
    • With any other advanced feature (data binding, styles, animations...), GetValue and SetValue are called directly and the wrapper is bypassed. There must be a dependency property registered with that name.

Comments

  • Anonymous
    December 06, 2012
    WHAT IS THE PLAN FOR THE NEXT VERSION OF WPF / SL?