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.
- Using
IValueConverter
to step into the debugger. - 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:
- The name of
<MyProperty>
is misspelled. - The property has been removed from the
<MySource>
type or not added yet. - The
<MySource>
type is not expected. E.g., it'sMyViewModel
but we expected it to beMyOtherViewModel
.
Solutions:
- The correct name of
<MyProperty>
(copy-pasting the property name will ensure 100% accuracy; remember, the real property name might be misspelled as well). - 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.
- How we go about solving this one is dependent on how the source is determined.
- The source is the
DataContext
. Handle theDataContextChanged
event of the target element in order to step into the debugger and find out where and why theDataContext
is not set to the expected value (just like when usingIValueConverter
). RelativeSource
is used; see: Cannot find source for binding with reference - solution 1.- 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:
- The
ElementName
property of the binding is set, but no element with that name could be found. - The
RelativeSource
property of the binding is set and theFindAncestor
mode is used, but no source was found which satisfied theRelativeSource
.
Solutions:
- 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.
- The first thing we should do is to verify that the
RelativeSource
is setup as we expected. That means make sureAncestorType
is the correct type and ifAncestorLevel
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 usePresentationTraceSources.TraceLevel
in order to increase the information we get from the binding engine. Notice however thatPresentationTraceSources.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:
- Lack of or Incorrect use of the
UpdateSourceTrigger
property of the binding. - The binding is using the
DataContext
as its binding source and theDataContext
isnull
. - One of the properties specified in the
Path
isnull
, e.g., User is null in the path "User.FirstName".
Solutions:
- Make sure
UpdateSourceTrigger
is set to the expected value. If the bindingsUpdateSourceTrigger
property isn't set, then it defaults to the value specified by the targetDependencyProperty
, e.g.,TextBox.Text
defaultUpdateSourceTrigger
-value isLostFocus
which means the source is only updated when theTextBox
loses focus. If the we want the binding to update whenever the target property changes, we'll setUpdateSourceTrigger
toPropertyChanged
. - If the
DataContext
or theDataContext
of an ancestor element is set through a binding then debug that binding. - Handle the
DataContextChanged
event of the target element in order to step into the debugger and find out where and why theDataContext
is set tonull
. If there is no code setting theDataContext
tonull
and thus theDataContextChanged
event isn't fired, then we'll have to debug the code that is supposed to set theDataContext
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:
System.Windows.Data Information: 40 : BindingExpression path error: 'MyProperty' property not found for 'object' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=MyProperty; DataItem=null; target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') System.Windows.Data Information: 19 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=MyProperty; DataItem=null; target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') System.Windows.Data Information: 20 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=MyProperty; DataItem=null; target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=MyProperty; DataItem=null; target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
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