ENTERPRISE

Windows Presentation Foundation in .NET 4 : Dependency Properties - Property Validation

5/15/2013 1:39:28 AM

When defining any sort of property, you need to face the possibility that it may be set incorrectly. With traditional .NET properties, you might try to catch this sort of problem in the property setter. With dependency properties, this isn't appropriate, because the property may be set directly through the WPF property system using the SetValue() method.

Instead, WPF provides two ways to prevent invalid values:

  • ValidateValueCallback. This callback can accept or reject new values. Usually, this callback is used to catch obvious errors that violate the constraints of the property. You can supply it as an argument to the DependencyProperty.Register() method.

  • CoerceValueCallback. This callback can change new values into something more acceptable. Usually, this callback is used to deal with conflicting dependency property values that are set on the same object. These values might be independently valid but aren't consistent when applied together. To use this callback, supply it as a constructor argument when creating the FrameworkPropertyMetadata object, which you then pass to the DependencyProperty.Register() method.

Here's how all the pieces come into play when an application attempts to set a dependency property:

  1. First, the CoerceValueCallback method has the opportunity to modify the supplied value (usually, to make it consistent with other properties) or return DependencyProperty.UnsetValue, which rejects the change altogether.

  2. Next, the ValidateValueCallback is fired. This method returns true to accept a value as valid or returns false to reject it. Unlike the CoerceValueCallback, the ValidateValueCallback does not have access to the actual object on which the property is being set, which means you can't examine other property values.

  3. Finally, if both these previous stages succeed, the PropertyChangedCallback is triggered. At this point, you can raise a change event if you want to provide notification to other classes.

1. The Validation Callback

As you saw earlier, the DependencyProperty.Register() method accepts an optional validation callback:

MarginProperty = DependencyProperty.Register("Margin",
  typeof(Thickness), typeof(FrameworkElement), metadata,
  new ValidateValueCallback(FrameworkElement.IsMarginValid));

You can use this callback to enforce the validation that you'd normally add in the set portion of a property procedure. The callback you supply must point to a method that accepts an object parameter and returns a Boolean value. You return true to accept the object as valid and false to reject it.

The validation of the FrameworkElement.Margin property isn't terribly interesting because it relies on an internal Thickness.IsValid() method. This method makes sure the Thickness object is valid for its current use (representing a margin). For example, it may be possible to construct a perfectly acceptable Thickness object that isn't acceptable for setting the margin. One example is a Thickness object with negative dimensions. If the supplied Thickness object isn't valid for a margin, the IsMarginValid property returns false:

private static bool IsMarginValid(object value)
{
    Thickness thickness1 = (Thickness) value;
    return thickness1.IsValid(true, false, true, false);
}

There's one limitation with validation callbacks: they are static methods that don't have access to the object that's being validated. All you get is the newly applied value. Although that makes them easier to reuse, it also makes it impossible to create a validation routine that takes other properties into account. The classic example is an element with Maximum and Minimum properties. Clearly, it should not be possible to set the Maximum to a value that's less than the Minimum. However, you can't enforce this logic with a validation callback because you'll have access only to one property at a time.

NOTE

The preferred approach to solve this problem is to use value coercion. Coercion is a step that occurs just before validation, and it allows you to modify a value to make it more acceptable (for example, raising the Maximum so it's at least equal to the Minimum) or disallow the change altogether. The coercion step is handled through another callback, but this one is attached to the FrameworkPropertyMetadata object, which is described in the next section.

2. The Coercion Callback

You use the CoerceValueCallback through the FrameworkPropertyMetadata object. Here's an example:

FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.CoerceValueCallback = new CoerceValueCallback(CoerceMaximum);

DependencyProperty.Register("Maximum", typeof(double),
  typeof(RangeBase), metadata);

The CoerceValueCallback allows you to deal with interrelated properties. For example, the ScrollBar provides Maximum, Minimum, and Value properties, all of which are inherited from the RangeBase class. One way to keep these properties aligned is to use property coercion.

For example, when the Maximum is set, it must be coerced so that it can't be less than the Minimum:

private static object CoerceMaximum(DependencyObject d, object value)
{
    RangeBase base1 = (RangeBase)d;
    if (((double) value) < base1.Minimum)

{
        return base1.Minimum;
    }
    return value;
}

In other words, if the value that's applied to the Maximum property is less than the Minimum, the Minimum value is used instead to cap the Maximum. Notice that the CoerceValueCallback passes two parameters—the value that's being applied and the object to which it's being applied.

When the Value is set, a similar coercion takes place. The Value property is coerced so that it can't fall outside of the range defined by the Minimum and Maximum, using this code:

internal static object ConstrainToRange(DependencyObject d, object value)
{
    double newValue = (double)value;
    RangeBase base1 = (RangeBase)d;

    double minimum = base1.Minimum;
    if (newValue < minimum)
    {
        return minimum;
    }
    double maximum = base1.Maximum;
    if (newValue > maximum)
    {
        return maximum;
    }
    return newValue;
}

The Minimum property doesn't use value coercion at all. Instead, once it has been changed, it triggers a PropertyChangedCallback that forces the Maximum and Value properties to follow along by manually triggering their coercion:

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

Similarly, once the Maximum has been set and coerced, it manually coerces the Value property to fit:

private static void OnMaximumChanged(DependencyObject d,
  DependencyPropertyChangedEventArgs e)
{
    RangeBase base1 = (RangeBase)d;
    ...
    base1.CoerceValue(RangeBase.ValueProperty);
    base1.OnMaximumChanged((double) e.OldValue, (double)e.NewValue);
}

The end result is that if you set conflicting values, the Minimum takes precedence, the Maximum gets its say next (and may possibly be coerced by the Minimum), and then the Value is applied (and may be coerced by both the Maximum and the Minimum).

The goal of this somewhat confusing sequence of steps is to ensure that the ScrollBar properties can be set in various orders without causing an error. This is an important consideration for initialization, such as when a window is being created for a XAML document. All WPF controls guarantee that their properties can be set in any order, without causing any change in behavior.

A careful review of the previous code calls this goal into question. For example, consider this code:

ScrollBar bar = new ScrollBar();
bar.Value = 100;
bar.Minimum = 1;
bar.Maximum = 200;

When the ScrollBar is first created, Value is 0, Minimum is 0, and Maximum is 1.

After the second line of code, the Value property is coerced to 1 (because initially the Maximum property is set to the default value 1). But something remarkable happens when you reach the fourth line of code. When the Maximum property is changed, it triggers coercion on both the Minimum and Value properties. This coercion acts on the values you specified originally. In other words, the local value of 100 is still stored by the WPF dependency property system, and now that it's an acceptable value, it can be applied to the Value property. Thus, after this single line of code executes, two properties have changed. Here's a closer look at what's happening:

ScrollBar bar = new ScrollBar();
bar.Value = 100;
// (Right now bar.Value returns 1.)
bar.Minimum = 1;
// (bar.Value still returns 1.)
bar.Maximum = 200;
// (Now now bar.Value returns 100.)

This behavior persists no matter when you set the Maximum property. For example, if you set a Value of 100 when the window loads and set the Maximum property later when the user clicks a button, the Value property is still restored to its rightful value of 100 at that point. (The only way to prevent this from taking place is to set a different value or remove the local value that you've applied using the ClearValue() method that all elements inherit from DependencyObject.)

This behavior is due to WPF's property resolution system, which you learned about earlier. Although WPF stores the exact local value you've set internally, it evaluates what the property should be (using coercion and a few other considerations) when you read the property.

NOTE

Long-time Windows Forms programmers may remember the ISupportInitialize interface, which was used to solve similar problems in property initialization by wrapping a series of property changes into a batch process. Although you can use ISupportInitialize with WPF (and the XAML parser respects it), few of the WPF elements use this technique. Instead, it's encouraged to resolve these problems using value coercion. There are a number of reasons that coercion is preferred. For example, coercion solves other problems that can occur when an invalid value is applied through a data binding or animation, unlike the ISupportInitialize interface.
Other  
 
Video tutorials
- How To Install Windows 8 On VMware Workstation 9

- How To Install Windows 8

- How To Install Windows Server 2012

- How To Disable Windows 8 Metro UI

- How To Change Account Picture In Windows 8

- How To Unlock Administrator Account in Windows 8

- How To Restart, Log Off And Shutdown Windows 8

- How To Login To Skype Using A Microsoft Account

- How To Enable Aero Glass Effect In Windows 8

- How To Disable Windows Update in Windows 8

- How To Disable Windows 8 Metro UI

- How To Add Widgets To Windows 8 Lock Screen
programming4us programming4us
Top 10
Free Mobile And Desktop Apps For Accessing Restricted Websites
MASERATI QUATTROPORTE; DIESEL : Lure of Italian limos
TOYOTA CAMRY 2; 2.5 : Camry now more comely
KIA SORENTO 2.2CRDi : Fuel-sipping slugger
How To Setup, Password Protect & Encrypt Wireless Internet Connection
Emulate And Run iPad Apps On Windows, Mac OS X & Linux With iPadian
Backup & Restore Game Progress From Any Game With SaveGameProgress
Generate A Facebook Timeline Cover Using A Free App
New App for Women ‘Remix’ Offers Fashion Advice & Style Tips
SG50 Ferrari F12berlinetta : Prancing Horse for Lion City's 50th
Popular Tags
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Exchange Server Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe Photoshop CorelDRAW X5 CorelDraw 10 windows Phone 7 windows Phone 8 Iphone