Debugging WPF data bindings

Introduction

Data bindings is one of the most widely used features of WPF. However, debugging data bindings is not as well known, which I'll attempt to rectify with this article.

There are essentially two methods we can use to debug our data bindings, when they do not throw an exception.

  1. Using IValueConverter to step into the debugger.
  2. Using the trace messages.

Using IValueConverter

This method is pretty straightforward. You'll create a class implementing the IValueConverter interface and then use this converter in your binding  so you can step into the debugger.

// Either you can use Debugger.Break() or put a break point
// in your converter, inorder to step into the debugger.
// Personally I like Debugger.Break() as I often use
// the "Delete All Breakpoints" function in Visual Studio.
public class MyDebugConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
           object parameter, CultureInfo culture)
    {
        System.Diagnostics.Debugger.Break();
        return value;
    }

    public object ConvertBack(object value, Type targetType, 
           object parameter, CultureInfo culture)
    {
        System.Diagnostics.Debugger.Break();
        return value;
    }
}

 

<TextBox Text="{Binding MyProperty, Converter={StaticResource MyDebugConverter}}"/>

Now whenever your binding provides a new value, the debugger will break at Debugger.Break() in your converter and you can step out to see which property  is set and where it was set.

When to use the IValueConverter method

The usefulness of this method is rather limited. It really does nothing but allows you to locate the piece of code that changes your property.  So you should use it when your binding is working, but you're not getting the expected value. If your binding source is using INotifyPropertyChanged,  then you could just put a breakpoint in the setter of your property in order to avoid using the converter.

A quick tip, if you're going to use this method, you might want to read this article by marlongrech, he demonstrates how  you can use the MarkupExtension so you don't have to make a resource entry for your converter.

Notice

If you use the Debugger.Break() and forget to remove the converter from your binding, you won't like the result when you make a release.

This is what happens when you run your application without a debugger attached to it.  According to the documentation, it should ask the user if they want  to attach a debugger, but that doesn't happen on my end.

Using trace messages

Details about a binding error is shown in your Output window whenever a binding fails to locate the binding source or a property on the source. You can use the key  shortcut Ctrl+Alt+O to activate your output window. The errors from data bindings will start with "System.Windows.Data Error: ##".

Binding errors

'<MyProperty>' property not found on 'object' ''<MySource>'

Reasons:

  1. The name of <MyProperty> is misspelled.
  2. The property has been removed from the <MySource> type or not added yet.
  3. The <MySource> type is not expected. E.g., it's MyViewModel but we expected it to be MyOtherViewModel.

Solutions:

  1. The correct name of <MyProperty> (copy-pasting the property name will ensure 100% accuracy; remember, the real property name might be misspelled as well).
  2. If removed, then the view might need to be reworked, so find out why the property has been removed before you go re-implementing the property (if it's even possible).  If it hasn't been added, be patient, your coworkers probably got as much on their plate as you.
  3. How we go about solving this one is dependent on how the source is determined.
    1. The source is the DataContext. Handle the DataContextChanged event of the target element in order to step into the debugger and find  out where and why the DataContext is not set to the expected value (just like when using IValueConverter).
    2. RelativeSource is used; see: Cannot find source for binding with reference - solution 1.
    3. The binding's Source property has been set; go to the XAML or code that sets this property and correct it.

Cannot find source for binding with reference

Reasons:

  1. The ElementName property of the binding is set, but no element with that name could be found.
  2. The RelativeSource property of the binding is set and the FindAncestor mode is used, but no source was found which satisfied the RelativeSource.

Solutions:

    1. Ensure we've not misspelled the name (do a copy-paste to be 100%sure); if that doesn't work, then the element with the given name is not in the appropriate namescope,  see WPF XAML Namescopes.
    2. The first thing we should do is to verify that the RelativeSource is setup as we expected.  That means make sure AncestorType is the correct type and if AncestorLevel is used, make sure the level is correct, keep in mind it's 1-based.  If the binding still doesn't find the binding source, then we can use PresentationTraceSources.TraceLevel  in order to increase the information we get from the binding engine. Notice however that PresentationTraceSources.TraceLevel was first introduced  in .NET 3.5 so if you're using .NET 3.0, you'll have to change your target framework for debugging purposes.

Let's have a look at the trace PresentationTraceSources.TraceLevel=High produces:

public class MyViewModel
{
    public string CurrentUserName {get; set}
    public IEnumerable<string /> UserActions { get { ... } }
}
<Window ...>
    <Window.DataContext>
        <MyViewModel>
    </Window.DataContext>
        <ListView ItemsSource="{Binding UserActions}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding DataContext.CurrentUserName,
                      RelativeSource={RelativeSource FindAncestor, 
                                      AncestorType={x:Type Button}},
                      PresentationTraceSources.TraceLevel=High}"/>
                    <TextBlock Text="{Binding}"/>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Window>

 

System.Windows.Data Warning: 63 : BindingExpression (hash=49385318): 
       Resolving source  (last chance)
System.Windows.Data Warning: 66 : BindingExpression (hash=49385318): 
       Found data context element: <null /> (OK)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried StackPanel (hash=17059405)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ContentPresenter (hash=29475730)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried Border (hash=51339472)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ListViewItem (hash=28574894)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried VirtualizingStackPanel (hash=25181126)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ItemsPresenter (hash=59408853)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ScrollContentPresenter (hash=56152722)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried Grid (hash=43844556)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ScrollViewer (hash=26847985)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried Border (hash=199777)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ListView (hash=8990007)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ContentPresenter (hash=1897140)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried AdornerDecorator (hash=18262443)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried Border (hash=16503569)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried MainWindow (hash=4463106)
System.Windows.Data Error: 4 : Cannot find source for binding 
       with reference 'RelativeSource FindAncestor, 
       AncestorType='System.Windows.Controls.Button', AncestorLevel='1''. 
       BindingExpression:Path=DataContext.CurrentUserName; DataItem=null; 
       target element is 'TextBlock' (Name=''); 
       target property is 'Text' (type 'String')

 

Notice the binding engine doesn't give up easely so we'll see the warnings repeated a few times. Just find the last "BindingExpression (hash=##...): Resolving source"-warning that precedes the error message. When we've done that, we will have an overview of the path  the binding engine takes to in order to find the source. It's now up to us to follow that path in our XAML/code to find the point at which we would have expected  the Button (in this case) to be and then fix the binding accordingly.

My binding doesn't work, but I do not get any error message

Reasons:

  1. Lack of or Incorrect use of the UpdateSourceTrigger property of the binding.
  2. The binding is using the DataContext as its binding source and the DataContext is null.
  3. One of the properties specified in the Path is null, e.g., User is null in the path "User.FirstName".

Solutions:

  1. Make sure UpdateSourceTrigger is set to the expected value. If the bindings UpdateSourceTrigger property isn't set, then it defaults  to the value specified by the target DependencyProperty, e.g., TextBox.Text default UpdateSourceTrigger-value is LostFocus  which means the source is only updated when the TextBox loses focus. If the we want the binding to update whenever the target property changes, we'll set UpdateSourceTrigger to PropertyChanged.
  2. If the DataContext or the DataContext of an ancestor element is set through a binding then debug that binding.
  3. Handle the DataContextChanged event of the target element in order to step into the debugger and find out where and why the DataContext is set  to null. If there is no code setting the DataContext to null and thus the DataContextChanged event isn't fired,  then we'll have to debug the code that is supposed to set the DataContext of the target element or an ancestor element.

In this case, you would want to use the IValueConverter method.

Monitoring existing bindings for errors

When we fix a bug, implement new features, or optimize code, we risk introducing new bugs like breaking a binding. Looking through the output window for binding errors  can be annoying as we'll have to filter out all the unrelated information ourselves. In order to filter out all the related information, we can use a listener and a switch  to log the trace messages to a file which we can then look through for binding errors. We log the data binding trace messages by adding the following to the configuration  section of our app.config.

<system.diagnostics>
    <sources>
        <source name="System.Windows.Data" switchName="mySwitch">
            <listeners>
                <add name="myListener" />
            </listeners>
        </source>
    </sources>

    <switches>
        <add name="mySwitch" value="All" />
    </switches>

    <sharedListeners>
        <add name="myListener" type="System.Diagnostics.TextWriterTraceListener"
             initializeData="DataBindingTrace.txt" />
    </sharedListeners>
</system.diagnostics>

Here we've set the switch to All; this will give us as much information as possible about the data binding. Setting it to Error will result  in only the data binding errors being logged. To turn the logging off completely, set it to Off.

Let's look at an example. The following binding fails, because the DataContext is null.

<TextBox DataContext="{x:Null}" Text="{Binding MyProperty}"/>

 

This produces the following output in our log file:

 

From the this, we can see at the first line that the binding source is null and thus the source of our error, which we debug as described  in Solution 1 of My binding doesn't work, but I do not get any error message.

Conclusion

I hope you've found this article informative. My goal was to communicate how and where we can find information about data binding errors as well as how we can interpret them.

Any feedback you may have is very welcome.

 

from:http://www.codeproject.com/Articles/244107/Debugging-WPF-data-bindings#bindingNotWorkignSolution1

posted @ 2012-10-31 17:20  sunnyboy  阅读(468)  评论(0编辑  收藏  举报