Setting metadata

Using the overloads of the PropertyMetadata constructor, we can optionally set a default value for the property and attach handlers to be called when the value changes, or when it is being re-evaluated. Let's update our example to attach a PropertyChangedCallback handler now:

public static readonly DependencyProperty HoursProperty = 
  DependencyProperty.Register(nameof(Hours), typeof(int), 
  typeof(DurationPicker), new PropertyMetadata(OnHoursChanged)); 
 
private static void OnHoursChanged(DependencyObject dependencyObject,  
  DependencyPropertyChangedEventArgs e)  
{ 
  // This is the signature of PropertyChangedCallback handlers 
} 

Note that our PropertyChangedCallback handler must also be declared as static in order to be used from the static context of the declared DependencyProperty as shown in the preceding code. However, we may have a situation where we need to call an instance method rather than a static method and in these cases, we can declare an anonymous method that calls our instance method like this:

public static readonly DependencyProperty HoursProperty = 
  DependencyProperty.Register(nameof(Hours), 
  typeof(int), typeof(DurationPicker), 
  new PropertyMetadata((d, e) => ((DurationPicker)d).OnHoursChanged(d,e)));   
 
private void OnHoursChanged(DependencyObject dependencyObject,  
  DependencyPropertyChangedEventArgs e)  
{ 
  // This is the signature of non-static PropertyChangedCallback handlers 
} 

Anonymous methods comprised of Lambda expressions can appear confusing, so let's first extract the relevant code:

(d, e) => ((DurationPicker)d).OnHoursChanged(d, e)) 

This could be re-written to make the example somewhat clearer:

(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
  => 
  ((DurationPicker)dependencyObject).OnHoursChanged(dependencyObject, e)) 

Now we can clearly see the input parameters of the PropertyChangedCallback handler, followed by the anonymous method body. Inside this method, we simply cast the dependencyObject input parameter to the type of the declaring class and then call the non-static method from the cast instance of the class, passing the input parameters through, if required.

As we saw in the Chapter 2, Debugging WPF Applications, the CLR properties that provide convenient access to our Dependency Properties will not be called by the WPF Framework when their values change. Using this PropertyChangedCallback handler is how we are able to perform actions upon value changes, or to debug the changing values.

The last overload of the PropertyMetadata constructor additionally enables us to set a CoerceValueCallback handler, which provides the platform for us to ensure that our values remain within valid ranges. Unlike the PropertyChangedCallback delegate, it requires us to return the output value of the property, so this enables us to alter the value before returning it. Here is a simple example that shows how we can adjust our property values:

public static readonly DependencyProperty HoursProperty = 
  DependencyProperty.Register(nameof(Hours), 
  typeof(int), typeof(DurationPicker), 
  new PropertyMetadata(0, OnHoursChanged, CoerceHoursValue));
                                                            
... 
   
private static object CoerceHoursValue(DependencyObject dependencyObject,  
  object value) 
{ 
  // Access the instance of our class from the dependencyObject parameter   
  DurationPicker durationPicker = (DurationPicker)dependencyObject; 
  int minimumValue = 1, maximumValue = durationPicker.MaximumValue; 
  int actualValue = (int)value; 
  return Math.Min(maximumValue, Math.Max(minimumValue, actualValue)); 
} 

In this simple example, we first cast the dependencyObject input parameter, so that we can access its MaximumValue property. Let's assume that our DurationPicker control can work with either twelve or twenty-four hour time formats and so we need to determine the current upper hour limit. We can therefore constrain our Hours property value to be between one and this upper limit.

When using the CoerceValueCallback handler, there is a special case that enables us to effectively cancel a change in value. If your code detects what your requirements specify to be a wholly invalid value, then you can simply return the DependencyProperty.UnsetValue value from the handler.

This value signals to the property system that it should discard the current change and return the previous value instead. You could even use this technique to selectively block changes to a property until a certain condition is met elsewhere in the class, for example.

That sums up the useful but fairly limited options that we have with our PropertyMetadata object, although it should be noted that there are a number of classes that derive from this class that we can use in its place and each have their own benefits. The UIPropertyMetadata class directly extends the PropertyMetadata class and adds the ability to disable all animations of the property value via its IsAnimationProhibited property.

Additionally, the FrameworkPropertyMetadata class further extends the UIPropertyMetadata class and provides us with the ability to set property inheritance, the default Binding.Mode and Binding.UpdateSourceTrigger values of the property, and a variety of FrameworkPropertyMetadataOptions flags that affect layout.

Let's take a look at some of the FrameworkPropertyMetadataOptions members. If we think that most users will want to use Two-Way data binding with our property, then we can declare it with the BindsTwoWayByDefault instance. This has the effect of switching the Binding.Mode from the default OneWay member to the TwoWay member on all bindings to our property:

public static readonly DependencyProperty HoursProperty = 
  DependencyProperty.Register(nameof(Hours), typeof(int), 
  typeof(DurationPicker), new FrameworkPropertyMetadata(0, 
  FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHoursChanged,  
  CoerceHoursValue)); 

Another commonly used flag is the Inherits instance, which specifies that the property value can be inherited by child elements. Think of the FontSize or Foreground properties that can be set on a Window and inherited by each control inside it.

Note that if we want to create a Dependency Property using this Inherits member, then we should declare it as an Attached Property, as property value inheritance works better with Attached Properties. We will find out more about this soon, in a subsequent section, but now let's continue. Next is the SubPropertiesDoNotAffectRender member, which can be used to streamline performance, and we'll find out more about this particular instance in Chapter 12, Deploying Your Masterpiece Application.

The last commonly used options are the AffectsArrange, AffectsMeasure, AffectsParentArrange and AffectsParentMeasure members. These are typically used with Dependency Properties that have been declared in custom panels, or other UI controls, where the property value affects the look of the control and changes to it need to cause a visual update.

It should also be noted that this FrameworkPropertyMetadataOptions enumeration is declared with the FlagsAttribute attribute, which signifies that we can also allocate a bitwise combination of its instance values, and therefore set multiple options for each of our Dependency Properties:

public static readonly DependencyProperty HoursProperty = 
  DependencyProperty.Register(nameof(Hours), typeof(int), 
  typeof(DurationPicker), new FrameworkPropertyMetadata(0, 
  FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | 
  FrameworkPropertyMetadataOptions.AffectsMeasure, OnHoursChanged, 
  CoerceHoursValue));

In order to set the default value for the Binding.UpdateSourceTrigger property, we need to use the most heavily populated constructor, passing all six input parameters:

public static readonly DependencyProperty HoursProperty = 
  DependencyProperty.Register(nameof(Hours), typeof(int), 
  typeof(DurationPicker), new FrameworkPropertyMetadata(0, 
  FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHoursChanged, 
  CoerceHoursValue, false, UpdateSourceTrigger.PropertyChanged)); 

Note that it is perfectly fine to pass null values for the callback handlers, if we don't need to use them. The false after the CoerceValueCallback handler value sets the IsAnimationProhibited property of the UIPropertyMetadata class. The UpdateSourceTrigger value set here will be used on all bindings to this property that have not explicitly set the UpdateSourceTrigger property on the binding, or have set the UpdateSourceTrigger.Default member to the binding property.

Now that we have fully investigated the various options that we have when we declare Dependency Properties using the Register method of the DependencyProperty class, let's take a look at the another registration method from this class.