This article covers many techniques you can use to troubleshoot and debug your WPF application, including binding errors, exception handling, inspecting the VisualTree and logging.
 

Introduction

 
Windows Presentation Foundation (WPF) is a joy to develop rich user experiences with. Animations, bindings, reskinnable codeless interfaces, all backed up with traditional .Net programming all make for a very rapid development cycle. However, when something goes wrong in that autogenerated super-sexy front-end, it is not always clear where the problem is.
 
This article will cover some of the methods for analyzing your WPF application, troubleshooting issues, and debugging seemingly obscure errors.  
 

Return to Top

Binding Errors

 
When an error occurs in the binding framework, the notification is usually "silent", in that is doesn't pop up an error dialog to the user. The error is instead displayed in the Output Window of Visual Studio. Although the errors seem obscure at first, there are only a few common ones and they are quite helpful messages.
 
Below are some typical errors and an explanation for each.
 
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.UserControl', AncestorLevel='1''. BindingExpression:Path=PartIds; DataItem=null; target element is 'ComboBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
 
This is a ComboBox inside a DataGridTemplateColumn. I am binding its ItemsSource to a property called PartIds. 
It is erroring when looking for the binding source, in other words the physical object to find the property on.
In this example, the error was because the source is a Window, not a UserControl.
 
 
"System.Windows.Data Error: 40 : BindingExpression path error: 'MyStringOptionz' property not found on 'object' ''MainWindow' (Name='Window')'. BindingExpression:Path=MyStringOptionz; DataItem='MainWindow' (Name='Window'); target element is 'ComboBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')"

This is when the property name in the binding expression is wrong, or the property does not exist in the source object.
In this example, the property name was misspelled with a 'z'.
 
Sometimes you will need a more detailed debugging output for binding. There are two ways to do this.
 

1. Changing Trace Level for All in Options

 
Click the Visual Studio menu Tools / Options, then open Debugging and Output Window. In there you can change the DataBinding output settings to Verbose or All.


(click to enlarge)
 

2. Changing Trace Level on a Specific Binding

 
If you increase the trace level that way, you will get a WHOLE LOAD of messages for everything. Far better to target the specific binding where the error is. To do this, you can call on an Attached Property PresentationSources.TraceLevel and set it for just the one binding.

<ComboBox ItemsSource="{Binding PartIdz, PresentationTraceSources.TraceLevel=High, RelativeSource={RelativeSource AncestorType=Window}}" . . . />

For more information on the Trace Levels and what they represent, read here.  

Return to Top

WPF Tree Visualizer


The WPF Tree Visualizer comes built into Visual Studio, and is an essential tool to know about.

With it, you can break into the Visual Tree and inspect the actual generated elements and their properties.

To use a Visualizer, you click on the magnifying glass next to a variable name in DataTips, a Watch window, or in the Autos, Locals, or Quick Watch window.



If you are visualizing a string object for example, you get a big grey text window. However, if you visualize a FrameworkElement, you get the WPF Tree Visualizer.


(click to enlarge)
 
For a Quick Start on how to use the WPF TV, try this : Debugging WPF - WPF Tree Visualizer + Helper Class
It also supplies a handy Attached Property you can drop in anywhere, without messing your code.  
 
The WPF Tree Visualizer is great for inspecting the VisualTree, but if you want to change any of the properties, you have to look at the "Locals" tab, as shown below.
 

(click to enlarge)
  
 

Other inspection tools


WPF Tree Visualizer is usually enough for the experienced developer to find the problem, however there are plenty of tools around if you prefer to defer.

Return to Top

Debuging Your Code

Apart from the WPF Tree Visualiser, the debugger in WPF applications is the main way to break into your application, trace through and follow program execution to the point of failure.

This is all standard Visual Studio stuff, and I won't waste time here reproducing what is available elsewhere:
Return to Top

Inner Exceptions

When your application raises an exception, and you are debugging in Visual Studio, you get the usual error dialog with the details. However, when the error is a XAML error, it is most frequently buried deeper in the exception details.

Within each exception is an Inner Exception, which is just another exception, but from a layer further down. If XAML throws an error about a missing resource, it re-raises the error as a meaningless "XAML Markup error". When you drill down deeper, you find what resource the actual error relates to.

Josh Smith wrote best documents the joy of inner exceptions here

Return to Top

Unhandled Exceptions


It is impossible to protect against every possible fault that could happen, when your application is "out in the wild". 

There are however, ways to try and improve on the standard Windows error dialogue, and stack dump, which will help you find the fault.
 
When an exception is unhandled by your application, there are several Unhandled Exception events that you can tap into and handle.
 
You can download and test all five handlers here : Handling Unhandled Exceptions in WPF (The most complete collection of handlers)


Application.DispatcherUnhandledException


This is triggered when the UI thread causes an unhandled exception. You can attach a handler to it either in App.xaml

<Application x:Class="MyProject.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml" DispatcherUnhandledException="App_DispatcherUnhandledException">
    <Application.Resources />
</Application>
 
... or App.xaml.cs
 
namespace MyProject
{
    public partial class App : Application
    {
        public App()
        {
            DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
        }

What you get in the handler arguments is a standard exception. Handle as you like, preferably write it to a permissions safe file location.

You can also set e.Handled to true if you want to try to overcome the error and move on

void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
    ProcessError(e.Exception);
    e.Handled = true;
}
 
Sometimes the error is too severe, and your application still crashes. But at least you have hopefully captured something useful, before it did.
 

AppDomain.CurrentDomain.UnhandledException

 
If the error is not on the UI thread, then the previous handler won't catch it. It is solely for the Dispatcher thread. To capture errors on any other thread, you need this second one.
 
This handler is attached in the Startup event handler, which can again be hooked into from mark-up:
 
<Application x:Class="UnhandledExceptionHandler.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml" Startup="App_Startup">
    <Application.Resources />
</Application>
 
... or in code:

namespace MyProject
{
    public partial class App : Application
    {
        public App()
        {
            Startup += new StartupEventHandler(App_Startup);
        }
 
        void App_Startup(object sender, StartupEventArgs e)
        {
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }
 
        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var exception = e.ExceptionObject as Exception;
            ProcessError(exception);
            if (e.IsTerminating)
                MessageBox.Show("Goodbye world!");
        }

 
Notice this uses an ExceptionObject, which is derived from Exception, so we can simply cast it back, for a generic exception handler, as shown below.
 
Also notice the ExceptionObject has an IsTerminating flag.
 

TaskScheduler.UnobservedTaskException


This exception happens when a Task has an "unobserved exception", and is about to trigger the "exception escalation policy", which by default terminates the process.
namespace MyProject
{
    public partial class App : Application
    {
        public App()
        {
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
        }

 
Again, we can try to prevent crashing, in this case by calling the e.SetObserved method, which marks the exception as "observed" and prevents exception escalation towards a termination (crash).
 
void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    ProcessError(e.Exception);
    e.SetObserved();
}
 

The catch-all Unhandled Handler!


Putting those three handlers together, we get the following:

namespace MyProject
{
    public partial class App : Application
    {
        public App()
        {
            DispatcherUnhandledException += App_DispatcherUnhandledException;
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
            Startup += new StartupEventHandler(App_Startup);
        }
 
        void App_Startup(object sender, StartupEventArgs e)
        {
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }
 
In the examples below, I show how to try and mark the exceptions as handled/observed, and continue execution. However, you may just want to close "gracefully", with a polite message, instead of an all-out crash.
 
One thing is certain, you will probably want a unified way of handling the exceptions, wherever they come from, as shown below.
 

Drilling down to include the Inner Exceptions

 
As mentioned before, the real reason for an error could be buried several layers down in Inner Exceptions. The three handlers above shared one error logging method, shown below, which iterates down till there are no more Inner Exceptions.
 
private void ProcessError(Exception exception)
{
    var error = "Exception = " + exception.Message;
 
    while(exception.InnerException != null)
    {
        exception = exception.InnerException;
        error += " : Inner Exception = " + exception.Message;
    }
             
    //This is where you save to file.
    MessageBox.Show(error);
}
 

Early attention to error handling in your application will always help production, because most bugs occur during development.

See Logging below.

 

Return to Top

Commenting Out and Binary Chopping

 
If a bug in your application is being particularly hard to find, there may be only one way left to find the culprit. It's very simple, and is often simply the fastest way to drill down to the offender.

The method is simply to remove (or comment out) sections of your code, or XAML, until you get execution PAST the error.

Then you start replacing parts of the removed code, until you find the exact control or method that is causing the problem.

It is referred to as binary chopping because you cut the code into two, to get past the error, then in two again, replacing half of the removed code, and so on until you get down to the offending code/xaml.

You can comment out huge chunks of your code or markup in Visual Studio with ctrl-k + c, then ctrl-k + u to uncomment. Both functions also available in the edit / advanced menu.

This is one of the advantages of writing your application to the MVVM design pattern. MVVM separates your UI (View) from the business logic supplied by the ViewModel, or the Model itself. This means the View is much more loosely coupled, and there are no hooks from code-behind, acting directly on controls, like referring to controls by name (x:Name) like MyTextBox.Text = "hello". In MVVM the emphasis is all on the bindings, so commenting out half your XAML just means less binding requests, and commenting out half your code/properties means more binding errors, but not a crash. 

Even an MVVM setup can have problems when you try to comment out half the markup. Controls may be relying on other controls, with direct biding in XAML. So there is always a certain amount of "knock-on" effects that also need commenting out, but experience often shows it is quite simply a very fast and effective way to find the sinner.

Just make sure you keep a copy of the original file (or have recently committed your code to source control) before such dramatic actions. Just in case you make an edit that looses your comment/cut history ;)
 
 
Return to Top


Is It Reproducable?

When you hit a brick wall, many people turn to the MSDN forums, for help and advice from other developers. Often, just the act of trying to put a problem into words for others to understand is equivalent to having a colleague look over your shoulder, which often catches those obvious "too close to the trees" kind of mistakes that we all make.

However, if the problem isn't obvious from your description, you will get a much quicker and accurate response from the forum if you supply a reproducable example of your problem in a few code snippets.

Quite often, when you try to reproduce the problem in a new project, you will often find the culpret anyway!

 
 
Return to Top

Logging

The other age old method of tracing bugs in your application, both in development and once released, is to keep a log of messages from every notable step of your application.
 
If your bug is of a composite nature (comprising of several different causes), or developing after several iterations, or at an unknown point, you will need to build a picture of what is happening, before you can even know where to start.
 

Logging Level

Writing comments and session data to a log file means scattering "WriteToLog" type methods throughout your code. It is therefore common to build a "LogLevel" parameter into your methods, allowing you to control the amount of spam that gets written. In a published application you would only want to capture critical messages, but with a config file flag or startup parameter that allows you to crank up the debug logging (logging ALL messages), for a client that experiences a bug.
 

Repository Choice

A log repository can be anything from a text file, to a database. One word of caution however. It is no good logging errors to a database if it was a database connection error. So file based logging for critical errors is strongly advised. Then you simply have a check method on application startup, for any waiting critical error files, that may have been generated when the application crashed. You can then 'process' them (into a database, or send by email) and then delete.

It is as explained, quite simple to create your own logging architecture for your application. The key to success is to START EARLY! Don't try to strap error handling or logging on later, as it will never be as useful as if you had started from the outset. Also, the majority of bugs are caused during development anyway, so early attention to error handling and logging may take slightly longer to code, but will help speed up the overall prodiuction process.
 

Why Reinvent the Wheel

You could have a lot of fun building your own error logging framework, that is thread safe and foolproof. Or if you're pushed for time or more interested in higher things, why not just adopt a tried and tested logging framework. Here are some options, but you should shop around, as new kids often come on the block.
 

Log4Net

One of the most established frameworks. "log4net is a tool to help the programmer output log statements to a variety of output targets. In case of problems with an application, it is helpful to enable logging so that the problem can be located. With log4net it is possible to enable logging at runtime without modifying the application binary. The log4net package is designed so that log statements can remain in shipped code without incurring a high performance cost. It follows that the speed of logging (or rather not logging) is crucial. At the same time, log output can be so voluminous that it quickly becomes overwhelming. One of the distinctive features of log4net is the notion of hierarchical loggers. Using these loggers it is possible to selectively control which log statements are output at arbitrary granularity. log4net is designed with two distinct goals in mind: speed and flexibility"
 
 

ELMAH

A very popular framework. "Stands for Logging Modules and Handlers. An application-wide error logging facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment."

 

NLog

Newer but 3rd popular "A logging platform for .NET with rich log routing and management capabilities. It can help you produce and manage high-quality logs for your application regardless of its size or complexity."

NuGet

All of the above frameworks are available through NuGet. If you don't already have NuGet, it is like an App store for Visual Studio application development. If you want a logging framework, or an MVVM framework, simply open the package manager and search. Results are listed by popularity. Click to install, and the package gets downloaded and integrated into your current project. That means all the dlls are added and registered, and any supporting files are added to your project. It takes all the pain out of adding supporting packages to your project. This is a must for all developers. No excuses, get it naow!!
 


Return to Top


See Also

 ↑ Return to Top


Community Resources

 

Return to Top