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.
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's MyViewModel
but we expected it to be MyOtherViewModel
.
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 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
).
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 the FindAncestor
mode is used, but no source was found which satisfied the RelativeSource
.
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 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:
- Lack of or Incorrect use of the
UpdateSourceTrigger
property of the binding.
- The binding is using the
DataContext
as its binding source and the DataContext
is null
.
- One of the properties specified in the
Path
is null
, e.g., User is null in the path "User.FirstName".
Solutions:
- 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
.
- If the
DataContext
or the DataContext
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 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:
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.