Archive for the ‘Value Coercion’ Category

Value Coercion for the Masses

Wednesday, May 5th, 2010

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