Jaa


Wha' Happened Part Two: More Property Changes in WPF

Introduction

Windows Presentation Foundation features quite a variety of different mechanisms that provide notifications of when a property changes its value. These mechanisms come from several different feature areas: from data binding, from the WPF property system, and also from the cluster of features around styling, templating and controls. This is Part Two of Two, and covers some of the scenarios that are more code than markup, and often involve creating custom classes. Part One concentrated on some of the lighter-weight techniques, where property changes could be addressed through markup techniques, using built-in WPF features, or both.

The following are the specific areas that Part Two will cover:

Creating a custom DependencyProperty, or overriding the metadata

To define an entirely new dependency property, or override the metadata on an existing one, you are automatically in a subclassing scenario. This means that you must decide on a way that you can provide your subclass to your application customer. Generally speaking, this is not a particularly daunting task if you are already expecting your users to install a WPF application as a client standalone executable. You just need to package your custom class in the executable itself, or in a library that is packaged with the executable. Techniques for this, and reasons for why to package in EXE or in DLL are outside the scope of this topic; see Deploying a Windows Presentation Foundation Application in the SDK.

Whether new or overriding metadata, the goal is to give a dependency property new metadata. The reason for that is because of the two tasty overridable callbacks you can find in the metadata: PropertyChangedCallback and CoerceValueCallback. This is where you get to specify the actual dependencies of a dependency property. The dependencies here are against other dependency properties. The goal is to create a matrix of dependencies, such that the set of properties you are working with can have their state be entirely self-maintained. You won't have to second guess whether a property's value is true at that moment before you get its value.

There's lots of other scenarios/patterns where dependencies between properties is useful. But the classic scenario we've described for dependency properties in the SDK is Minimum<-Current->Maximum. At the very least, you want to enforce that Current is greater than Minimum, and less than Maximum. This is pretty easy to do if Minimum and Maximum are fixed values, you can do it in a standard setter. But what if Minimum and Maximum also have dependencies, and can change because of user action or external state? You could still produce that pattern using plain CLR properties, but it would require even more logic in each property's setter, including calling other property setters directly. It would be very easy to lose track of the relationships. You also might end up calling that code even if the property value remains the same.

So, let's conceptualize the design of Minimum, Current, and Maximum, based on the assumption that Minimum and Maximum can change as well as Current. Let's further define the scenario by stating that Current changes either by user action or by coercion because of Minimum or Maximum. Minimum or Maximum are not user settable, but changes come from some source your application tracks, and are reported to users in the UI.

An example scenario for this relationship is a control that tracks inventory as part of a shopping application. Minimum (and the default) would generally be one, but perhaps certain items have a minimum order other than one. Maximum would be whatever the inventory system told you is the available quantity. Both these values would sensibly be obtained by databinding to something like an ADODB datasource, although Minimum probably wouldn't change as often. (As the control author, you are expecting that the properties are databound once the control is in place, but making each property a dependency property allows the control user to do what they want in the long run.) Current is the user selectable part. The purpose of the control would be to track whether the user's desired order can still be filled as they shop, up until the final order is submitted to the cart. If any quantity was no longer available, the control should a) adjust the Current value up or down depending on new constraints b) notify the user that something about their cart contents was changed. There's ways to accomplish something similar with data validation, but having a true control dedicated to this task is appealing too.

There are actually several conceivable patterns even within Minimum/Current/Maximum, but one clear pattern this suggests for dependency properties is:

Minimum has a PropertyChangedCallback. It calls CoerceValue on Current, and also rejects any property change that would attempt to put Minimum over Maximum.

Current has a CoerceValueCallback. This is called by the property system any time a new value is provided by any means, and is also specifically called by Minimum and Maximum to enforce the dependency. Within that CoerceValueCallback, you enforce that Current is always stored as within its current min/max constraints. Current also could have a PropertyChangedCallback. You wouldn't change this property here (that's already happened), and for this scenario you wouldn't change other properties either, but you could raise a custom event that would be helpful for user notification.

Maximum has a PropertyChangedCallback. It calls CoerceValue on Current, and also rejects any property change that would attempt to put Maximum under Minimum.

In code, here's what this might look like (along with some extra stuff like the property wrappers, and a simple property-level validation callback that keeps quantity positive):

using System;

using System.Windows;

using System.Windows.Controls;

namespace SDKSample

{

  public class ShoppingCartInfo : Control

  {

      public ShoppingCartInfo() : base() { }

    public static bool IsValidQuantity(object value)

    {

        Int32 v = (Int32)value;

        return (v>=0);

    }

    public static readonly DependencyProperty CurrentQuantityProperty = DependencyProperty.Register(

        "CurrentQuantity",

        typeof(Int32),

        typeof(ShoppingCartInfo),

        new FrameworkPropertyMetadata(

            0,

            new PropertyChangedCallback(OnCurrentQuantityChanged),

            new CoerceValueCallback(CoerceCurrentQuantity)

        ),

        new ValidateValueCallback(IsValidQuantity)

    );

    public int CurrentQuantity

    {

      get { return (Int32)GetValue(CurrentQuantityProperty); }

      set { SetValue(CurrentQuantityProperty, value); }

    }

    private static object CoerceCurrentQuantity(DependencyObject d, object value)

    {

      ShoppingCartInfo sci = (ShoppingCartInfo)d;

      int CurrentQuantity = (Int32)value;

      if (CurrentQuantity < sci.MinimumQuantity) CurrentQuantity = sci.MinimumQuantity;

      if (CurrentQuantity > sci.MaximumQuantity) CurrentQuantity = sci.MaximumQuantity;

      return CurrentQuantity;

    }

    private static void OnCurrentQuantityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

    {

       //TODO: raise event

    }

    public static readonly DependencyProperty MaximumQuantityProperty = DependencyProperty.Register(

        "MaximumQuantity",

        typeof(Int32),

        typeof(ShoppingCartInfo),

        new FrameworkPropertyMetadata(

            0,

            new PropertyChangedCallback(OnMaximumQuantityChanged),

            new CoerceValueCallback(CoerceMaximumQuantity)

        ),

        new ValidateValueCallback(IsValidQuantity)

    );

    public int MaximumQuantity

    {

      get { return (Int32) GetValue(MaximumQuantityProperty); }

      set { SetValue(MaximumQuantityProperty, value); }

    }

    private static void OnMaximumQuantityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

    {

        d.CoerceValue(MinimumQuantityProperty);

        d.CoerceValue(CurrentQuantityProperty);

    }

    private static object CoerceMaximumQuantity(DependencyObject d, object value)

    {

      ShoppingCartInfo sci = (ShoppingCartInfo)d;

      Int32 max = (Int32)value;

      if (max < sci.MinimumQuantity) return DependencyProperty.UnsetValue;

      else return max;

    }

    public static readonly DependencyProperty MinimumQuantityProperty = DependencyProperty.Register(

      "MinimumQuantity",

      typeof(Int32),

      typeof(ShoppingCartInfo),

      new FrameworkPropertyMetadata(

        0,

        new PropertyChangedCallback(OnMinimumQuantityChanged),

        new CoerceValueCallback(CoerceMinimumQuantity)

      ),

      new ValidateValueCallback(IsValidQuantity)

    );

    public int MinimumQuantity

    {

      get { return (Int32) GetValue(MinimumQuantityProperty); }

      set { SetValue(MinimumQuantityProperty, value); }

    }

  private static void OnMinimumQuantityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

    {

      d.CoerceValue(MaximumQuantityProperty);

      d.CoerceValue(CurrentQuantityProperty);

    }

    private static object CoerceMinimumQuantity(DependencyObject d, object value)

    {

        ShoppingCartInfo sci = (ShoppingCartInfo)d;

        int min = (Int32)value;

        if (min > sci.MaximumQuantity) return DependencyProperty.UnsetValue;

        else return min;

    }

  }

}

The code here is a variation of an SDK sample that uses a slightly different pattern for the dependencies. I actually like this pattern better because it is less complex, and gives a cruder but easier approach for handling Minimum<->Maximum.

OnPropertyChanged

OnPropertyChanged is a callback that is defined by DependencyObject. As such, it is functionality that is at the root of the dependency property system. But that callback doesn't get overridden all that much within the existing WPF APIs. And it's not expected that all that many developers will override it either. Why? Because it's typically too big a tool for all but the biggest jobs.

OnPropertyChanged at the DependencyObject gets called once every time that any dependency property value as stored by that DependencyObject changes its value (a true effective value change; the property system is smart enough to not call callbacks on set operations that result in unchanged values). The exact property that changed is available to the callback, but the point is still that the callback is called once for ANY such dependency property. Usually you want your ability to detect property changes to be a little more surgical than that, which is entirely possible by overriding metadata and changing PropertyChangedCallback as decribed earlier. OnPropertyChanged is more useful if you are somehow working at or near an architecture level, where you might care about large numbers of related properties and the fact that they are changing. What's that 'large number'? Indeterminate. But consider this: if one or more properties of a DependencyObject are animated, the callback gets called pretty much constantly. So in general, the WPF itself only uses OnPropertyChanged to deal with crucial features, such as recompositing layout, or when an object maintains clean/dirty state that is either faster or more object-specific than the property system's builtin capabilities.

Summary: although its API name is tempting, you should think hard before overriding, and verify to your own satisfaction and by your own methodology that implementing it is not a possible performance bottleneck for your application (or your framework, if you've gotten ambitious).

Detecting Changes to Collections

Some of this was already covered in Part One, where we talked about data binding and INotifyCollectionChanged. But what if data binding is not involved? If for instance you have a property that takes a collection as its value, and you want to know whether there has been a change in the collection contents?

It turns out that the builtin property change notifications do not necessarily handle this case well. Pure .NET collection support doesn't handle it so well either. Adding an item to a collection doesn't change the reference to the collection that that the property stores, so no change is reported to the property system. If you were to have the property system deal with this, you would have to implement an object that is both a DependencyObject and a working collection itself (as opposed to having one or more properties that take collections). That combination of classes isn't very practical.

However, there is salvation ... at a price. WPF also ships a generic collection class called FreezableCollection. There are no existing properties that take FreezableCollection. So applying FreezableCollection support for a property is entirely a home brew exercise, for those of you building out libraries of objects. But if you need to track collection changes closely, you might want to give this class a try. FreezableCollection isn't necessarily a collection of Freezables, it's only constrained to DependencyObject, a base class for Freezable. A FreezableCollection is an object that is aware of subproperty changes to its Items and can promote those subproperty changes to concerned listeners as being changes to itself.

Freezables

One architectural purpose of the Freezable class is to support animated value type properties. In order to do this, a Freezable also has to report subproperty changes, such as from within a structure. Without this, there wouldn't be any way to notify the WPF retained mode render system that an animation required a new render pass based on subproperties.

Another reason why Freezables track their subproperties is because subproperty changes might be coming from unmanaged resources. If you are using Freezables from code, you can tell when a Freezable changes because it raises the Changed event. However, that event doesn't route, so attempting to handle the event at the application level often isn't practical.

Freezables, by the way, are a nonsealed class, in contrast to DependencyProperty. It'll be interesting to see whether the developer community comes up with ways to use Freezables that transcend animating properties, because it's theoretically possible.

Resources

You have two choices for how you reference ResourceDictionary resources in WPF: StaticResource, or DynamicResource. StaticResource can be quite restrictive; its core scenario is when you define large numbers of resources in XAML files. But if you're making thousands of resource references, it has the advantage of not creating intermediate expressions for each of them that might never even get used. DynamicResource isn't as restrictive; its core scenario is when you want resources that really only exist the way that you want them at runtime.

As far as detecting changes to properties, StaticResource is obviously a nonstarter. Not only is the resource static as the name implies, it's also something that needs to be findable at the moment in the XAML parsing sequence that the resource is requested.

There's not anything about DynamicResource per se that gives you a resource change notification. However, you can only assign a DynamicResource value to a dependency property. Therefore, you would just use the property level notification on a dependency property to detect changes. More info: Resources Overview.

I've probably missed at least one other obvious feature area that touches on WPF property changes. However, I doubt there is going to be a Part Three; there's other things I want to cover too!

- wolf

Comments