Isn't it really annoying when WPF binding errors fail silently? The
application compiles, the application runs, but nothing is working - all
because of a silly typo in a binding somewhere. And then, once you
realize it is a binding error (which is not always obvious), you have to
drudge through the debug output trying to find that one line that says
"System.Windows.Data Error ....". Even worse than that is the subtle
binding error where the app still works, but maybe the text on some
label isn't updating right, and you don't even notice the bug until
weeks later.
If you are as annoyed by all that as I am, you have come to the right
place. Today we are going to take a look at a simple way to make binding
errors literally explode in your face (as MessageBoxes!). We are going
to take a look at adding a custom trace listener to listen for binding
errors and pop them up as a message box when they happen.
To do this, we have to create a custom trace listener - our own class
derived from
DefaultTraceListener.
Trace listeners are behind almost all of the debugging/error reporting
in .NET - it is where almost all the debug output comes from. Probably
your most direct interaction with trace listeners comes from using
Debug.WriteLine
and Debug.Assert
- trace listeners are used there to
do the debug output and the message box in case of failure.
Our custom trace listener is actually a really simple chunk of code, so
I'm just going to put it all here in one block and then walk through it.
using System.Diagnostics;
using System.Text;
using System.Windows;
namespace SOTC_BindingErrorTracer
{
public class BindingErrorTraceListener : DefaultTraceListener
{
private static BindingErrorTraceListener _Listener;
public static void SetTrace()
{ SetTrace(SourceLevels.Error, TraceOptions.None); }
public static void SetTrace(SourceLevels level, TraceOptions options)
{
if (_Listener == null)
{
_Listener = new BindingErrorTraceListener();
PresentationTraceSources.DataBindingSource.Listeners.Add(_Listener);
}
_Listener.TraceOutputOptions = options;
PresentationTraceSources.DataBindingSource.Switch.Level = level;
}
public static void CloseTrace()
{
if (_Listener == null)
{ return; }
_Listener.Flush();
_Listener.Close();
PresentationTraceSources.DataBindingSource.Listeners.Remove(_Listener);
_Listener = null;
}
private StringBuilder _Message = new StringBuilder();
private BindingErrorTraceListener()
{ }
public override void Write(string message)
{ _Message.Append(message); }
public override void WriteLine(string message)
{
_Message.Append(message);
var final = _Message.ToString();
_Message.Length = 0;
MessageBox.Show(final, "Binding Error", MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
}
So first off, actually setting the trace listener.
SetTrace
here is a
static method that takes a
SourceLevel
and
TraceOptions.
SourceLevel
is essentially how much tracing to do. In our case here
Error
is a good value - otherwise we might get bombarded with message
boxes. TraceOptions
is a flag enum that says what types of information
should be included in the trace. In general, I've found that you don't
need any of the extra info to debug data binding errors, so I default it
to None
.
When
SetTrace
is called, we create an instance of our custom trace
listener (unless our custom trace listener is already listening). Then
we add it to the list of listeners on the DataBindingSource
trace.
PresentationTraceSources
has a number of different traces, but today
all we care about is the DataBindingSource
. However, if you ever
happen to be looking for other WPF debug information, this class is a
good place to start.
Once our listener instance is added to the list, it will now be notified
if there are any errors. This is where our overrides to the
Write
and
WriteLine
methods come in. The base methods for DefaultTraceListener
do things like write debug output - but in this case we don't want to do
that. In our case we want to show a message box with the error. However,
because we don't want to be bombarded with message boxes, we only show
one for every line written - we just accumulate the strings passed in to
the Write
call until a WriteLine
call is made, at which point we
display the whole error string.
And finally, as you might expect, the static method
CloseTrace
removes
the trace listener from the list, so it won't be notified of errors
anymore.
So how do we use this trace listener, and what does it show when there
is an error? Well, below you can see a nice binding error waiting to
happen:
<Window x:Class="SOTC_BindingErrorTracer.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Button Content="{Binding ElementName=foo, Path=bar}" />
</Window>
The trace only needs to be set once, and then works for the entire
application until it is closed. so in this case we set up the trace in
the constructor for this widow:
public partial class Window1 : Window
{
public Window1()
{
BindingErrorTraceListener.SetTrace();
InitializeComponent();
}
}
And now, when you start the application, you get the following very
obvious error:
And that is it! Using this, now binding errors are as obvious as a
regular old exception -and it is much less likely that one will slip in
and stay in by mistake. You can grab the source for the custom listener
below, if you would like to use it/modify it yourself.
Source Files:
Comments
Post a Comment