Friday, January 23, 2015

Detecting Binding Errors - WPF PresentationTraceSources.DataBindingSource

By default, Visual Studio (v2012 onwards) is configured to automatically log any binding errors detected whilst running a WPF application.

You can see examples of such in the Debug Output window:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyUnknownProperty' property not found on 'object' ''Product' (HashCode=30581329)'. BindingExpression:Path=MyUnknownProperty; DataItem='Product' (HashCode=30581329); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
This setting can be adjusted using the Debugging, Output Window settings: 



Often, it's useful to be notified in a more intrusive manner when a binding error has been detected...rather than silently continuing, just as WPF does.

In order to add your own handling, you need to add a class that derives from TraceListener to the PresentationTraceSources.DataBindingSource.Listeners collection.


Your TraceListener needs to implement the following abstract methods:

public override void WriteLine(string message)
public override void Write(string message)

Generally, you'll only add implementation details to the WriteLine method.  When a binding error is detected the WPF will call your WriteLine and Write methods.

Full Source:

Listing

internal class BindingListener : TraceListener{

    public static readonly BindingListener Current = new BindingListener();
 
    private const int Uninitialised = 0;
    private const int Initialised = 1;
    private int _initialised = Uninitialised;
 
    public void Initialise()
    {
        if (Interlocked.CompareExchange(ref _initialised, Initialised, Uninitialised) == Uninitialised)
        {
            PresentationTraceSources.DataBindingSource.Listeners.Add(this);
        }
    }
 
    public override void WriteLine(string message)
    {
        throw new ApplicationException(message);
    } 
    public override void Write(string message)
    {}
}

In the above example we wrap access to the BindingListener with a static Current property.  When Initialise() is called, provided it's the first time is has been called, then the class is added to PresentationTraceSources.DataBindingSource.Listeners 



It's probably overkill using Interlocked.CompareExchange for thread-safety as generally you'll be calling Initialise() from the main GUI thread, as your app starts up:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        BindingListener.Current.Initialise();
        base.OnStartup(e);
    }
}
When you run the application (in debug mode) and there is any form of binding error, you'll see following exception dialog: 


This can be quite annoying, but at least you won't miss any binding errors.
One thing to note: your listener only gets called in Debug builds!

No comments: