Click here to Skip to main content
15,860,859 members
Articles / Desktop Programming / WPF
Article

XAML Debugger Visualizer for WPF

Rate me:
Please Sign up or sign in to vote.
4.93/5 (22 votes)
15 Nov 2007CPOL10 min read 67.3K   1.1K   49   5
A debugger visualizer that allows WPF controls and objects to be visualized as XAML.

A meaningful way to visualize variables in the debugger

The Autos, Locals and the four watch windows provided by the Visual Studio IDE allow you to inspect and modify variable values. However, for viewing complex objects having many properties and fields, this is not the friendliest interface to inspect values. The main issue is the lack of an overall picture. For example, for a DataTable, you may want to visualize the data in the table without inspecting the Rows collection and the DataRow objects it contains. This detailed view of variables, though useful, is not always the perfect visual for debugging.

Fortunately, debuggers in Visual Studio 2005 and Visual Studio 2008 have a feature called Debugger Visualizer which allows an alternative and friendly way to view complex objects. A good example of this is the DataSet Visualizer which shows the actual data of the DataTable in a grid and also allows you to modify it. It provides a high level view of the DataTable which is sometimes useful to quickly figure out bugs or issues. Visualizers can be easily written for custom types, and if you are a framework author, you can supply visualizers for types in your framework.

While working with WPF, I found it a little painful that there was no easy way to visualize the UI hierarchy while debugging. Then, I found a neat debugger visualizer that visualized the WPF visual tree, by David Sleeckx. After using that for some time, it struck to me that it will be neat to have a XAML visualizer which will allow visualizing the XAML representation of an object and also allowing modifying the object by modifying the XAML. I personally have used the visualizer extensively to modify templates in a debugging session. This allows me to test things without cancelling the debugging session or starting a new build. In the next section, I will take you on a guided tour of the visualizer and we will see it in action.

Using the XamlVisualizer - A guided tour

If you have not already downloaded and installed the visualizer, go ahead and do so as per the instructions in the Download and Installation section. Assuming you have followed the steps in the Download and Installation section correctly, you are now ready to use the visualizer. Let's start with a quick debugging session. I have used VS 2008 to develop this step by step tutorial, but the instructions are equally valid if you have VS 2005 and the Visual Studio Tools for Orcas installed.

First, let's start off by creating a new WPF project. For the sake of this tutorial, we will create a new WPF application in C#. Let's name the project XamlVisualizerTest as shown in the screenshot below. Also, notice the combo box in the upper right corner whose selected value is ".NET Framework 3.0". This is the new multi-targeting feature of VS 2008. When you select .NET Framework 3.0, you indicate that you will like to use the WPF/WCF/WF technologies which are available in .NET Framework 3.0 but you don't want to use the .NET Framework 3.5 features such as LINQ. What VS 2008 does in such a case is that it does not add references to LINQ assemblies. But you are free to use C# 3.0 features such as anonymous classes, lambda expressions, and auto implemented properties - these features are not specific to LINQ or .NET Framework 3.5.

Image 1

As a courtesy, the wizard creates Window1.xaml, Window1.xaml.cs, App.xaml, and app.xaml.cs (though in real world apps, I have to always delete the Window1.xaml file, so it is more of a pain than a courtesy). Open the Window1.xaml.cs file and put a breakpoint at the end of the constructor. To immediately see the effect of the debugger, run the debugging session (press F5). The break point will be hit in the constructor as shown below:

Image 2

Notice the magnifying glass icon on the extreme right in the Value column for the variable this. Clicking on it will lead to the XAML Visualizer being shown:

Image 3

Notice that the XAML code is not the same as the XAML you specified in the Window1.xaml file. This is because the XAML shown by the XAML Visualizer is the run-time representation of the object. So if a property specified in XAML gets modified programmatically, then the XAML visualizer will show the programmatically modified value. Also, notice that the element name is Window1 instead of Window as we specified in the Window1.xaml. Again, the reason is that the XAML used in Window1.xaml is used to define the Window1 class, whereas the XAML shown by the XAML Visualizer is the representation of any object with the type Window1. Finally, notice that the Replace button is disabled as it is not possible to modify the this object.

Viewing more complex XAML content

A common task when working in WPF is to use control templates. Even if you don't specify a control template, a control inherits a template automatically from the theme. The default control templates for controls used by WPF are available for all the themes supported by WPF with the Windows SDK samples. But in this section, we will see how we can get the markup of a template using the XAML Visualizer.

To view the control template, we need to have more interesting content in our window. So, let's modify Window1.xaml to include a Button, as shown below:

XML
<Window x:Class="XamlVisualizerTest.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">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition  />
            <RowDefinition Height="40" />
        </Grid.RowDefinitions>
        <Button Grid.Row="1" HorizontalAlignment="Right" Margin="0,9,9,11" 
                Name="button1" Width="75">Button</Button>
    </Grid>
</Window>

Let's launch the debugger again, and when the debugger hits the breakpoint in InitializeComponent, type the expression button1.Template in the watch window, as shown in the screenshot below.

screenshot of the template

As you can see in screenshot, the XAML for the default template in all its details is shown in the XAML Visualizer window. Also, notice that the Replace button is active, which gives us something to do in the next section.

Replacing the XAML content

One nice feature of modern debuggers is that they allow the values of variables to be modified during the debugging process. The same ability is also available in the XAML Visualizer. You can modify the XAML code in the visualizer window and see the effects in the application being debugged. To try it out, let's modify the XAML content of button1's template as shown below:

Modifying the XAML

The change is highlighted in the above screenshot. We change the BackgroundColor to Blue, and the result, unsurprisingly, appears as shown below:

Image 6

We managed to change the color to blue without modifying the main application code. It's all between the debugger and debuggee, the main code still remains unmodified. If you stop the debugger and re-run the application, then the button's appearance will revert back.

Replacing the XAML is powerful but...

Let me start this section by saying that XAML replacement will not always work. In this section, we will understand why and when it will not work. Let's run our sample app again in the debugger, and break in the InitializeComponent function. This time, let's visualize the button1 variable as shown below and replace the contents as shown in the highlighted portion of the screenshot below:

Replacing the button

You will expect that the button text will change once you click the Replace button. But alas, this does not happen. You will see that the original text remains. Is this a bug? Although this behavior is not expected on the outset, if we give it a more careful thought, we will realize that the visualizer did exactly what it was supposed to do. It replaced the button1 member variable with the XAML supplied in the visualizer dialog. The button1 member variable held a reference to the button in the WPF visual tree, and now it refers to the new object created by the XAML visualizer. Replacing the member variable does not mean that the content is replaced in the visual tree. In fact, if you visualize the button1 variable again, you will see that it has the new XAML we supplied. This is an important thing you need to be aware of when using the XAML visualizer.

How Visualizers work?

In this section, I will quickly go through the internal working of the XAML Visualizer. XAML Visualizer has three main classes as shown below:

Class diagram

The DialogDebuggerVisualizer and the VisualizerObjectSource belong to the VS IDE. The DialogDebuggerVisualizer is the most important class, and a visualizer should have a class derived from DialogDebuggerVisualizer. The DialogDebuggerVisualizer has a method called Show which is invoked by the IDE when the user wants to display a value using the visualizer. It is here that the visualizer displays the dialog, gets any changes by the user, and asks Visual Studio to replace the object if the user intends to do so.

C#
protected override void Show(IDialogVisualizerService windowService, 
          IVisualizerObjectProvider objectProvider)
{
    //The stream to which the dat ais written can be obtained by calling GetData
    Stream dataStream = objectProvider.GetData();
    
    //Replace displayForm with your own custom Form or Control.
    using (XamlVisualizerForm displayForm = new XamlVisualizerForm())
    {
        displayForm.XamlText = new StreamReader(dataStream).ReadToEnd();
        displayForm.IsObjectReplaceable = objectProvider.IsObjectReplaceable;
        if (windowService.ShowDialog(displayForm) == DialogResult.OK)
        {
            //After the dialog is shown the user 
            //may click replace to replace the object
            MemoryStream replacementDataStream = new MemoryStream();
            using (XmlWriter xmlWriter = 
                   XmlTextWriter.Create(replacementDataStream))
            {
                xmlWriter.WriteRaw(displayForm.XamlText);
            }

            replacementDataStream.Seek(0, SeekOrigin.Begin);
            //Replace the object in the debuggee
            objectProvider.ReplaceData(replacementDataStream);
        }
    }
}

The objectProvider parameter passed to the Show method is used to obtain the actual object to display in the Visualizer. In our case, this is the streamed XAML. Data is transferred back and forth between the debuggee process and the debugger process using streams. In our case, this stream consists of the XAML representation. At this point, you might be wondering how the stream got the XAML in the first place? This is where the XamlVisualizerObjectSource comes into picture. The XamlVisualizerObjectSource has methods that run both in the debuggee process and the debugger process. The method which runs in the debuggee process is called GetData, and it is responsible for writing the contents of the object being visualized to a stream. In the case of the XAML Visualizer, we directly use the XamlWriter class to write the contents to the stream.

C#
/// <summary>
/// This function is used to write a representation of the object to a stream.
/// The visualizer can then use the data to display
/// the object. The default implementation
/// of this function serailzes the object using
/// Binary formatter. But in our implementation we will
/// write the contents of the object as Xaml to the stream
/// </summary>
/// <param name="target">The target object</param>
/// <param name="outgoingData">The stream
///       where the XAML needs to be written to</param>
public override void GetData(object target, System.IO.Stream outgoingData)
{
    //Although the XamlWriter class has a member that writes to the
    //stream, we use an XmlWriter. This is because we want the output to be
    //indented.
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.OmitXmlDeclaration = true;
    settings.Indent = true;
    settings.IndentChars = "   "; //3 spaces
    settings.NewLineOnAttributes = true;
    using (XmlWriter writer = 
           XmlWriter.Create(outgoingData, settings))
    {
        XamlWriter.Save(target, writer);
    }
}

It is this stream which is obtained by the objectProvider.GetData call in the Show method discussed above. The other method of interest is CreateReplacemntObject, which takes a stream passed from the debugger and converts it to an object which will replace the object in the debugger.

C#
/// <summary>
/// This method is called on the debuggee side. The purpose of this method is to 
/// take the target object that is being visualized and a stream and return an
/// object that can replace the object being visualized. In our implementation 
/// we simply use the XamlReader object to reconstruct
/// an object from the Xaml content
/// provided in the stream.
/// </summary>
/// <param name="target">The object being visualized</param>
/// <param name="incomingData">The stream that 
///        contains data sent from the debugger</param>
/// <returns>An object that replaces the target object</returns>
public override object CreateReplacementObject(object target, 
                       System.IO.Stream incomingData)
{
    return XamlReader.Load(incomingData);
}

It is no surprise that we are using XamlReader to load the data and create the replacement object. So now, we have a DialogDebuggerVisualizer, a VisualizerObjectSource, and an object of a given type being visualized. How do we link all three? This is done through an assembly attribute named DebuggerVisualizerAttribute, as shown below:

XML
//Apply XamlVisualizer Visualizer to a WPF Visual
[assembly: DebuggerVisualizer(typeof(XamlVisualizer.XamlVisualizer), 
    typeof(XamlVisualizer.XamlVisualizerObjectSource), 
    Target=typeof(Visual), Description = "XAML Visualizer")]

//Apply XamlVisualizer Visualizer to a WPF Style
[assembly: DebuggerVisualizer(typeof(XamlVisualizer.XamlVisualizer), 
    typeof(XamlVisualizer.XamlVisualizerObjectSource),
    Target = typeof(Style), Description = "XAML Visualizer")]

//Apply XamlVisualizer Visualizer to templates
[assembly: DebuggerVisualizer(typeof(XamlVisualizer.XamlVisualizer), 
    typeof(XamlVisualizer.XamlVisualizerObjectSource),
    Target = typeof(FrameworkTemplate), Description = "XAML Visualizer")]

In the above code, we indicate that we want to use the XAML Visualizer to visualize WPF Visuals, Styles, and Templates. If you want to add more types (for example, a FlowDocument), you can do so by adding an attribute as follows:

XML
//Apply XamlVisualizer Visualizer to documents
[assembly: DebuggerVisualizer(typeof(XamlVisualizer.XamlVisualizer), 
    typeof(XamlVisualizer.XamlVisualizerObjectSource),
    Target = typeof(FlowDocument), Description = "XAML Visualizer")]

You can add this attribute to all objects or all DependencyObjects if you want to, but that is not recommended as XamlWriter and XamlReader may not always work.

Installing the Visualizer

To install the Visualizer:

  1. Download the binary for the appropriate version from the download link on the top of this article.
  2. Extract the zip and copy the XamlVisualizer.dll to My Documents\Visual Studio 2005\Visualizers for VS2005, or to My Documents\Visual Studio 2008\Visualizers for VS2008.

Update

I started writing this article about two months back, though I have been using the visualizer for a long time. By the time the article got posted, Josh Smith had posted Woodstock for WPF, which is similar to the WPF tree visualizer by David Sleeckx. Both XAML Visualizer and Woodstock can be used together.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionVS2010 error: "Could not load this custom viewer" Pin
RedDk4-Sep-14 6:49
RedDk4-Sep-14 6:49 
QuestionVS2010 Pin
ragang9-Jul-12 1:38
ragang9-Jul-12 1:38 
GeneralGreat Pin
Przemyslaw Celej17-Nov-07 11:28
Przemyslaw Celej17-Nov-07 11:28 
GeneralI guess MS is just too busy these days... Pin
cheartwell17-Nov-07 4:00
cheartwell17-Nov-07 4:00 
GeneralFantastic Pin
Josh Smith16-Nov-07 5:50
Josh Smith16-Nov-07 5:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.