Value Coercion for the Masses

My latest snippets package adds value coercion support to Silverlight dependency properties. The mechanism for coercing values is identical to the CoerceValueCallback approach used in WPF.

What is value coercion?

If you are not familiar with value coercion, it is simply a mechanism by which related (or “interdependent”) dependency properties can be kept in sync and valid.

The quintessential example can be found within the Slider control. A Slider has both Minimum and Maximum properties. Clearly, it would be a problem if the Maximum value were allowed to fall below the Minimum value. Value coercion is used to prevent this invalid state from occuring.

In WPF, the Slider control (or more specifically, the RangeBase control) ensures that these property values stay valid by using a feature of the dependency property metadata called a CoerceValueCallback. Whenever the Maximum property value changes, it is passed through this coercion function. If the Maximum value happens to be less than the Minimum value, the function will coerce it to be equal to the Minimum value so that it is valid.

The coercion routine for the Maximum property looks something like this:

private static object CoerceMaximum(DependencyObject d,
    object value)
{
    double min = ((RangeBase)d).Minimum;
    double max = (double)value;
    if (max < min) return min;
    return value;
}

Whenever the related Minimum property changes, the control explicitly coerces the Maximum property. This ensures that the Maximum value stays valid with respect to the new Minimum value.

The property changed callback for the Minimum property looks similar to this:

private static void OnMinimumChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
     ((RangeBase)d).CoerceValue(MaximumProperty);
    ((RangeBase)d).CoerceValue(ValueProperty);
}

Notice that changes to the Minimum property also result in coercion of the Slider’s Value property. That’s because the Value property also needs to stay valid. More specifically, it must be kept between the Minimum and Maximum values. That means if either the Minimum or Maximum values change, the Value property must explicitly be coerced. As such, the property changed callback for the Maximum property also coerces the Value property, something like this:

private static void OnMaximumChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    ((RangeBase)d).CoerceValue(ValueProperty);
}

Clearly, the Value property needs its own coercion routine to keep it between Minimum and Maximum. For completeness, here is what the CoerceValueCallback for the Value property looks like:

private static object CoerceValue(DependencyObject d,
    object value)
{
    double min = ((RangeBase)d).Minimum;
    double max = ((RangeBase)d).Maximum;
    double val = (double)value;
    if (val < min) return min;
    if (val > max) return max;
    return value;
}

Base Value vs. Effective Value

It is important to recognize that with value coercion, a dependency property has both a “base” (or “desired”) value and an “effective” (or “coerced”) value. The “base” value is always passed into the CoerceValueCallback and the value returned from that method becomes the new “effective” value.

In the case of the Minimum and Maximum example, if the “base” value of the Maximum property is less than the Minimum value, then the “effective” value of the Maximum property becomes equal to the Minimum value. Otherwise, the “base” and “effective” values are simply equal.

Value Coercion is Not Natively Supported in Silverlight

There’s nothing more frustrating than needing to port something from WPF to Silverlight and realizing that a key feature like value coercion does not yet exist in Silverlight. (For the record, I have no idea whether it will ever be supported natively, but if you use the mechanism provided by my snippets, you should be in great shape if/when we get true native support for value coercion in Silverlight.)

The lack of support for value coercion in Silverlight means you must roll a “do-it-yourself” version of the feature in your classes. The Silverlight Slider control attempts to do this. Unfortunately, this can lead to code that is cumbersome to maintain, especially if you need to use the same mechanism in several different classes.

Furthermore, achieving parity between frameworks can prove to be very difficult. As evidence of this, note that the pseudo-coercion within Silverlight’s native Slider control is just wrong. The Minimum property coerces the Value property upward, but does not coerce it back down correctly (and it’s likewise wrong for the Maximum property).

Value Coercion — There’s a Snippet for That!

To support value coercion in a consistent manner across your Silverlight classes, you can leverage my FrameworkPropertyMetadata class and the related SilverlightCoercionHelper class. Simply add a class file to your project called FrameworkPropertyMetadata.cs and then expand the “dp shc” snippet (Dependency Property — Silverlight Helper Classes) within it.

Whenever you declare a dependency property with a CoerceValueCallback (e.g., the “dp s3” snippet), you also need to initialize the owner type for value coercion and add a CoerceValue method to the class. To do this, expand the “dp scm” snippet (Dependency Property — Silverlight Coercion Methods) within your class.

To see a demonstration of the snippets in action, check out the video at the end of this post.

Cross-Framework Compatibility

If you use my snippets to support value coercion in Silverlight, your code should also compile just fine in WPF. When compiled in WPF, the framework’s native coercion mechanism will be used.

To support this cross-framework compatibility, you will notice that the helper classes and coercion methods in my snippets wrap certain code blocks within the #IF SILVERLIGHT directive.

Video Introduction to Value Coercion

I’ve put together a 14-minute screencast demonstrating all of this. Specifically, this video illustrates how value coercion works within the WPF and Silverlight native Slider controls. It then shows how to use my snippets to create similar interdependent Minimum, Maximum, and Value properties in a custom Silverlight control.

NOTE: The audio in this video has been “coerced” to protect the guilty! πŸ˜‰

You canΒ download the source code here for the Silverlight 4 sample coercion application created within the video.

I hope this helps a few folks maintain portable WPF/Silverlight code! πŸ™‚

Cheers!
Dr. WPF

5 Responses to “Value Coercion for the Masses”

  1. Vishal says:

    Hello,

    I found this site while I was searching results for WPF in google. I found this site very informative and I must say you know WPF inside out. I have added this site to my list of favorites.

    Regards.

  2. wekempf says:

    As usual, great stuff! I’d put the helper class in a library rather than using a snippet every time, but that’s just me. πŸ™‚

  3. Dr. WPF says:

    Thanks @wekempf. Yep, I use the helper class from a library, myself, as a practical matter. The same is true for several of the WPF helper classes that are included in my snippets (RoutedEventHelper, RoutedCommandHelper, etc). I rarely have a need to expand these templates because they are in my core MVVM library.

    Since several other snippets rely on the helper classes, I include them in snippet form just to ensure the snippet package is complete for others. But it definitely makes sense to expand them into a library for anyone who plans to use them across projects. πŸ™‚

  4. Andrew Smith says:

    Excellent! I may have missed it if you mentioned it but one other subtle downside besides the one you list in the source about e.NewValue being the base value in SL is that if you explicitly set the Value to what you consider to be the effective/coerced value, you will not be able to update the base value to that value since you won’t get a property changed callback.

  5. Dr. WPF says:

    Very good point, Andrew. That’s a scenario that could definitely come into play, and currently you would have to change the property value to some other valid value and then set it back to the desired value in order to update the base value. I will update the comments in the source with this information.

    I might also add a helper method that just updates the base value to match the effective value (for people who are dilligent enough to watch for edge scenarios).

    The only way I can think of to avoid fringe cases like this is if they add true coercion support to the property engine in Silverlight, but I’m guessing it isn’t very high on the priority list.