More on debugging DependencyProperty

In the previous post on DependencyPropertyHelper, we explored one way of debugging the Dependency Properties.

The DepedencyPropertyHelper provides you details on from which value provider the final value was calculated from. However, if you want to trace and ensure the binding has been set correctly, you could make use of PresentationTraceSources.

For example, consider following binding.

<Button Content="{Binding ButtonTitle}"/>

To enable tracing, one could enable the PresentationTraceSources as,

<Button Content="{Binding ButtonTitle, diag:PresentationTraceSources.TraceLevel=High}"/>

Do not forget to add reference to System.Diagnostics

xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"

Now your output window would have the entire trace.

System.Windows.Data Warning: 60 : BindingExpression (hash=28316044): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=28316044): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=28316044): Attach to System.Windows.Controls.Button.Content (hash=17324607)
System.Windows.Data Warning: 67 : BindingExpression (hash=28316044): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=28316044): Found data context element: Button (hash=17324607) (OK)
System.Windows.Data Warning: 71 : BindingExpression (hash=28316044): DataContext is null
System.Windows.Data Warning: 65 : BindingExpression (hash=28316044): Resolve source deferred
System.Windows.Data Warning: 67 : BindingExpression (hash=28316044): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=28316044): Found data context element: Button (hash=17324607) (OK)
System.Windows.Data Warning: 78 : BindingExpression (hash=28316044): Activate with root item ShellViewModel (hash=18700393)
System.Windows.Data Warning: 108 : BindingExpression (hash=28316044):   At level 0 - for ShellViewModel.ButtonTitle found accessor RuntimePropertyInfo(ButtonTitle)
System.Windows.Data Warning: 104 : BindingExpression (hash=28316044): Replace item at level 0 with ShellViewModel (hash=18700393), using accessor RuntimePropertyInfo(ButtonTitle)
System.Windows.Data Warning: 101 : BindingExpression (hash=28316044): GetValue at level 0 from ShellViewModel (hash=18700393) using RuntimePropertyInfo(ButtonTitle): 'Proceed'
System.Windows.Data Warning: 80 : BindingExpression (hash=28316044): TransferValue - got raw value 'Proceed'
System.Windows.Data Warning: 89 : BindingExpression (hash=28316044): TransferValue - using final value 'Proceed'

You could also set write custom TraceListener to break at the binding error. For example,

PresentationTraceSources.Refresh();
PresentationTraceSources.DataBindingSource.Listeners.Add(new ConsoleTraceListener());
PresentationTraceSources.DataBindingSource.Listeners.Add(new DebugTraceListener());
PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error;

Where DebugTraceListener is defined as

public class DebugTraceListener : TraceListener
{
    public override void Write(string message)
    {
    }

    public override void WriteLine(string message)
    {
        Debugger.Break();
    }
}

This would ensure a break point is hit whenever encountered with a missing binding.

DependencyPropertyHelper.GetValueSource : Debugging Helper for Dependency Properties

DependencyPropertyHelper.GetValueSource is a great debugging aid in detecting the source of value of a Dependency Property. This is quite useful for WPF developers who might need to figure out the source which provides the current value for the Dependency Property.

The DependencyPropertyHelper.GetValueSource returns a structure ValueSource which has 5 Properties.

  • BaseValueSource
  • IsAnimated
  • IsCoerced
  • IsCurrent
  • IsExpression
DependencyPropertyHelper.GetValueSource(buttonControl,CustomValueProperty)
{System.Windows.ValueSource}
    BaseValueSource: Local
    IsAnimated: false
    IsCoerced: false
    IsCurrent: false
    IsExpression: true

Dependency Property Value Precedence

The value of Dependency Property is calculated in 5 Steps.

  • Calculate the Base Value
  • Evaluate Expression if any
  • Apply Animation if any
  • Coerce Value

The BaseValueSource in the ValueSource structure speficies how the Base Value of the Dependency property is calculated. The base value of Dependency Property is calculated with aid of property value providers. Some ofthe Value Providers in decreasing Order of precedence are

  • Local Value
  • Templated Parent
  • Implicit style
  • Style triggers
  • Template Triggers
  • Style Setters
  • Theme
  • Inheritence
  • Default Value

Once the base value has been determined, the value is then passed through a series of steps depending on how the value is configured. These include Expressions, Animations and coerce callbacks if they are configured. You can follow more details on Dependency Property Value Precedence in the MSDN documentation

The DependencyPropertyHelper.GetValueSource provides an indication on how the current value in Dependency Property has been determined.

Why be wary of Value Coercion in Dependency Properties

If you are not quite familiar with Value Coercion, it allows you to change/correct value of a property, when it is assigned an unexpected value. This also allows you to ensure relative properties are also kept in sync or in other words, allows you to enforce relation between properties of an object. For example, Date of Birth should not exceed Date of Demise, or Minimum Value in a slider should not exceed the Maximum Value.

You could achieve such a synchronization mechanism using Value Coercion. Consider the following code.

// Note that code has a 'little problem', we will discuss it shortly.
public int MinimumValue
{
    get { return (int)GetValue(MinimumValueProperty); }
    set { SetValue(MinimumValueProperty, value); }
}

public static readonly DependencyProperty MinimumValueProperty = 
    DependencyProperty.Register(nameof(MinimumValue), typeof(int), typeof(Configuration), new PropertyMetadata(0, null, new CoerceValueCallback(OnMinimumValueCoerce)));

private static object OnMinimumValueCoerce(DependencyObject d, object baseValue)
{
    if(d is Configuration config && baseValue is int newValue)
    {
        var maxValue = config.MaximumValue;
        var oldValue = config.MinimumValue;
        return maxValue > newValue ? newValue : oldValue;
    }
    return baseValue;
}

public int MaximumValue
{
    get { return (int)GetValue(MaximumValueProperty); }
    set { SetValue(MaximumValueProperty, value); }
}

public static readonly DependencyProperty MaximumValueProperty =
    DependencyProperty.Register(nameof(MaximumValue), typeof(int), typeof(Configuration), new PropertyMetadata(0,null,new CoerceValueCallback(OnMaximumCoerce)));

private static object OnMaximumCoerce(DependencyObject d, object baseValue)
{
    if(d is Configuration config && baseValue is int newValue)
    {
        var minValue = config.MinimumValue;
        var oldValue = config.MaximumValue;
        return newValue > minValue ? newValue : oldValue;
    }
    return baseValue;
}

In the above code, you are using the CoerceValueCallback within the PropertyMetada to ensure MinimumValue and MaximumValue are kept in sync with each other. Every time a new value is assigned for MinimumValue, it checks if the value is exceeds the MaximumValue. If yes, it reverts the changes and retains the old value.

Little Problem

While the above code looks seemingly harmless, there is a small problem associated with. If you examine the code, we have coercing values for both MinimumValue and Maximum value. Now imagine a scenario when are using the Dependency properties in your code.

<controls:Configuration MinimumValue="{Binding MinValue}"  MaximumValue="{Binding MaxValue}" />

Consider the initial values of MinValue and MaxValue are 1 and 100. At the first glance, this looks valid value, however if you execute the code, you would realize the MinimumValue nevers gets set. Instead, the OnMinimumValueCoerce comes into play and reverts the changes. This is becase, at first the MinimumValue is set (by the order in which is defined in our Xaml – Change the order and behavior would be different).

When a value 1 is assigned to MinimumValue, the OnMinimumValueCoerce notices that it exceeds the current value of MaximumValue (which hasn’t changed yet and is still default of 0). This causes the values to reverts as per our logic in OnMinimumValueCoerce.

PropertyChangedCallback Vs CoerceValueCallback Vs ValidateValueCallback

At this point, it is worth noticing the difference between the 3 seemingly similiar callbacks associated with DependencyProperty – PropertyChangedCallbackCoerceValueCallback and ValidateValueCallback.

The difference could be summarized as

  • PropertyChangedCallback – Reacts to a value change
  • ValidateValueCallback – Determine if the value is valid
  • CoerceValueCallback – Coerce a value.

Order of execution

  1. ValidateValueCallback
  2. CoerceValueCallback
  3. PropertyChangedCallback

Solution

One solution that could be applied in this scenario is

  • Allow MinimumValue to be set to any value
  • In PropertyChangedCallback of MinimumValue, force coercion of MaximumValue.
  • In CoerceValueCallback of MaximumValue, check if it is less than MinimumValue and set it to MinimumValue.

For example,

public int MinimumValue
{
   get { return (int)GetValue(MinimumValueProperty); }
   set { SetValue(MinimumValueProperty, value); }
}

public static readonly DependencyProperty MinimumValueProperty = 
   DependencyProperty.Register(nameof(MinimumValue), typeof(int), typeof(Configuration), new PropertyMetadata(0, new PropertyChangedCallback(OnMinimumValueChanged)));

private static void OnMinimumValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
   if(d is Configuration config)
   {
       config.CoerceValue(MaximumValueProperty);
   }
}

public int MaximumValue
{
   get { return (int)GetValue(MaximumValueProperty); }
   set { SetValue(MaximumValueProperty, value); }
}

public static readonly DependencyProperty MaximumValueProperty =
   DependencyProperty.Register(nameof(MaximumValue), typeof(int), typeof(Configuration), new PropertyMetadata(0,null,new CoerceValueCallback(OnMaximumCoerce)));

private static object OnMaximumCoerce(DependencyObject d, object baseValue)
{
   if(d is Configuration config && baseValue is int newValue)
   {
       var minValue = config.MinimumValue;
       return newValue > minValue ? newValue : minValue;
   }
   return baseValue;
}

As you can see, while 3 muskeeters of PropertyChangedCallbackCoerceValueCallback and ValidateValueCallback are extremly useful, one needs to be aware of similiar issues which could come along and hence one needs to be aware of what is the difference between them and the sequence of execution.