Writing a Debugger Visualizer in WPF: Part 1

1. Introduction

There are a lot of good examples of how to create a debugger visualizer for Visual Studio. One thing that is common among all of them, is that they are all written using Windows form. I got a question on the Microsoft forum asking whether it is possible to make debugger visualizer in WPF. I decided to do an experiment. After playing with it a little bit, I realized that it is possible to make a debugger visualizer even in WPF. This opens a whole new door for me, because now data can be displayed in a much more sophisticated way using modern technology.

2. Background

Let's take a look at the first step. To make a debugger visualizer, we have to create a class that is inherited by DialogDebuggerVisualizer class. This class is defined in using Microsoft.VisualStudio.DebuggerVisualizers; namespace, so we have to include its reference too. This namespace defines other classes and interfaces for not only writing the debuger visualizer, but also to debug the visualizer itself. Here is a block diagram to show the classes and interfaces in this namespace.

All of these classes are inherited directly by Object class the grand daddy of .Net framework.Here is a class diagram of all classes define in Microsoft.VisualStudio.DebuggerVisualizer namespace.

There is only one method to overload in the DialogDebuggerVisualizer class, and here is the signature of that method.

protected override void Show(IDialogVisualizerService windowService, 
              IVisualizerObjectProvider objectProvider)

The class diagram of this class is very simple. Here is a class diagram of this class. The method inherited by Object class isn't shown.

Now here is a little catch. In a traditional Windows form debugger, we are going to call the ShowDialog method of IDialogVisualizerService interface that will internally call the Windows form. IdialogVisualizerService interface has only one method ShowDialog. This method is overloaded and can accept only CommonDialog, Control or Form. Here is a class diagram of this interface.

If we want to make a WPF based visualizer, then we have to create a Window object and call it ourselves. In its simplest way it is something like this:

protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{

    // set the attributes of WPF window
    Window win = new Window();
    win.Title = "My Visualizer";
    win.Width = 400;
    win.Height = 300;
    win.Background = Brushes.Blue;
    win.WindowStartupLocation = WindowStartupLocation.CenterScreen;

    win.ShowDialog();
}

Don't forget to add the references of "WindowsBase", "PresentationCore", "PresentationFramework" and "Microsoft.VisualStudio.DebuggerVizualiers", in the project.Here is a screen shot of Solution Explorer after adding the reference of WPF dlls and debugger vizualisers dll.

This will display the WPF visualizer window with a blue background. Let's do little bit more and make one working application. This is just a proof of concept, so we are making one small application. Our visualizer works only with Int32 data type. We defined it at the form of attribute when defining the namespace for our visualizer.

[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(MyVisualizer.MyVisualizerClass), typeof(VisualizerObjectSource),
Target = typeof(System.Int32),Description = "My Visualizer")]
namespace MyVisualizer
{

}

Here text set into the "Description" property will display as a context menu when this vizualizer is loaded during the debugging. We select Int32 data type just for simplicity because this type is also serializeable. If we want to make visualizer of our custom data type or any other non serializeable data type then we have to override GetData method of class that implements IVisualizerObjectProvider interface.

Now we have two ways to display our data if we are going to use any template to for better output. One simple, fast and preferred way is to write XAML and load that XAML programmatically. The other approach is to make everything programmatically.

Writing a Debugger Visualizer in WPF: Part 1

3. Defining Data template with Code

Our target is to display the value of integer variable in four different formats. We will display integer in decimal, hex decimal, octal and binary format. Let's first make a small function for base conversion. I picked this function from CodeProject originally written by "Balamurali Balaji" and modified it little bit to handle the negative number. Here is modified version of base convertor.

// Originally written by Balamurali Balaji
// Changed little bit to handle the negative sign
// http://www.codeproject.com/KB/cs/balamurali_balaji.aspx
private string DecimalToBase(int number, int basenumber)
{
    string strRetVal = "";
    const int base10 = 10;
    char[] cHexa = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
    int[] result = new int[32];
    int MaxBit = 32;
    bool isNegative = false;

    if (number < 0)
    {
        isNegative = true;
        number *= -1;
    }

    for (; number > 0; number /= basenumber)
    {
        int rem = number % basenumber;
        result[--MaxBit] = rem;
    }

    for (int i = 0; i < result.Length; i++)
    {
        if ((int)result.GetValue(i) >= base10)
        {
            strRetVal += cHexa[(int)result.GetValue(i) % base10];
        }
        else
        {
            strRetVal += result.GetValue(i);
        }
    }

    strRetVal = strRetVal.TrimStart(new char[] { '0' });

    if (isNegative)
    {
        strRetVal = strRetVal.Insert(0, "-");
    }

    return strRetVal;
}

We also make one small class for type and value to store the base name and converted value in that base. Here is the implementation of that class.

public class TypeValue
{
    public TypeValue()
    {

    }

    public TypeValue(String type, String value)
    {
        Type = type;
        Value = value;
    }

    public String Type
    { get; set; }

    public String Value
    { get; set; }
}

Then we are going to create list of this class and store all the converted values in it. Then assign it to list box.

List listType = new List();

Int32 obj = (Int32)objectProvider.GetObject();

listType.Add(new TypeValue("Decimal", obj.ToString()));
listType.Add(new TypeValue("Hex", obj.ToString("X")));
listType.Add(new TypeValue("Octal", DecimalToBase(obj, 8)));
listType.Add(new TypeValue("Binary", DecimalToBase(obj, 2)));

listBox.ItemsSource = listType;

Before that we did all the dirty work for data binding and defining data template and visual tree of data template.

We also added one more static method in the class just for the debugging purpose. It is not necessary to add this method in this class, but it will help us to debug the vizualier.

// This function is only for debugging purpose
public static void TestShowVisualizer(object obj)
{
    VisualizerDevelopmentHost host = 
       new VisualizerDevelopmentHost(obj, typeof(MyVisualizerClass));
    host.ShowVisualizer();
}

Here is the complete code of this visualizer.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.DebuggerVisualizers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Data;

[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(MyVisualizer.MyVisualizerClass), typeof(VisualizerObjectSource),
Target = typeof(System.Int32),Description = "My Visualizer")]
namespace MyVisualizer
{
    public class MyVisualizerClass : DialogDebuggerVisualizer
    {
        protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            // set the attributes of WPF window
            Window win = new Window();
            win.Title = "My Visualizer";
            win.Width = 400;
            win.Height = 300;
            win.Background = Brushes.Blue;
            win.WindowStartupLocation = WindowStartupLocation.CenterScreen;

            ListBox listBox = new ListBox();
            listBox.Margin = new Thickness(10);
            listBox.HorizontalContentAlignment = HorizontalAlignment.Stretch;

            DataTemplate dt = new DataTemplate();

            Binding bindingType = new Binding();
            bindingType.Path = new PropertyPath("Type");

            Binding bindnigValue = new Binding();
            bindnigValue.Path = new PropertyPath("Value");

            FrameworkElementFactory textType = new FrameworkElementFactory(typeof(TextBlock));
            textType.SetBinding(TextBlock.TextProperty, bindingType);
            textType.SetValue(Control.ForegroundProperty, Brushes.Blue);
            textType.SetValue(Control.FontWeightProperty, FontWeights.Bold);

            FrameworkElementFactory textValue = new FrameworkElementFactory(typeof(TextBlock));
            textValue.SetBinding(TextBlock.TextProperty, bindnigValue);
            textValue.SetValue(Control.ForegroundProperty, Brushes.Black);


            FrameworkElementFactory stack = new FrameworkElementFactory(typeof(StackPanel));
            stack.SetValue(Control.MarginProperty, new Thickness(5));
            stack.AppendChild(textType);
            stack.AppendChild(textValue);

            FrameworkElementFactory border = new FrameworkElementFactory(typeof(Border));
            border.SetValue(Control.BackgroundProperty, Brushes.LightYellow);
            border.SetValue(Control.BorderBrushProperty, Brushes.Brown);
            border.SetValue(Control.BorderThicknessProperty, new Thickness(3));
            border.SetValue(Control.MarginProperty, new Thickness(5));
            border.AppendChild(stack);

            dt.VisualTree = border;
            listBox.ItemTemplate = dt;

            List<TypeValue> listType = new List<TypeValue>();

            Int32 obj = (Int32)objectProvider.GetObject();

            listType.Add(new TypeValue("Decimal", obj.ToString()));
            listType.Add(new TypeValue("Hex", obj.ToString("X")));
            listType.Add(new TypeValue("Octal", DecimalToBase(obj, 8)));
            listType.Add(new TypeValue("Binary", DecimalToBase(obj, 2)));

            listBox.ItemsSource = listType;

            win.Content = listBox;
            win.ShowDialog();
        }

        // This function is only for debugging purpose
        public static void TestShowVisualizer(object obj)
        {
            VisualizerDevelopmentHost host = new VisualizerDevelopmentHost(obj, typeof(MyVisualizerClass));
            host.ShowVisualizer();
        }

        // Orignally written by Balamurali Balaji
        // Changed little bit to handle the negative sign
        // http://www.codeproject.com/KB/cs/balamurali_balaji.aspx
        private string DecimalToBase(int number, int basenumber)
        {
            string strRetVal = "";
            const int base10 = 10;
            char[] cHexa = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
            int[] result = new int[32];
            int MaxBit = 32;
            bool isNegative = false;

            if (number < 0)
            {
                isNegative = true;
                number *= -1;
            }


            for (; number > 0; number /= basenumber)
            {
                int rem = number % basenumber;
                result[--MaxBit] = rem;
            }


            for (int i = 0; i < result.Length; i++)
            {
                if ((int)result.GetValue(i) >= base10)
                {
                    strRetVal += cHexa[(int)result.GetValue(i) % base10];
                }
                else
                {
                    strRetVal += result.GetValue(i);
                }
            }

            strRetVal = strRetVal.TrimStart(new char[] { '0' });

            if (isNegative)
            {
                strRetVal = strRetVal.Insert(0, "-");
            }

            return strRetVal;
        }
    }

    public class TypeValue
    {
        public TypeValue()
        {
        }

        public TypeValue(String type, String value)
        {
            Type = type;
            Value = value;
        }

        public String Type
        { get; set; }

        public String Value
        { get; set; }
    }
}

Now let's make a small program to test this visualizer. In this small program we just create one integer variable and going to see its value in our vizualizer.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MyVisualizer;

namespace MyVisualizerClient
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            int iValue = -100;

            MyVisualizerClass.TestShowVisualizer(iValue);
            Console.WriteLine(iValue);
        }
    }
}
When we copy the visualizer DLL at specified location (in my computer it is C:\Program Files\Microsoft Visual Studio 9.0\Common7\Packages\Debugger\Visualizers) then for all integer variable we can see the "My Visualizer" option in context menu during debugging.

[DebuggerVisualizers_06.gif]

It is also available at watch window as shown in this screen shot.

[DebuggerVisualizers_07.gif]

And when we clicked on it then we can see our visualizer window with list box in it to display the value of integer in hex, octal and binary format. Here is the output of this.

Writing a Debugger Visualizer in WPF: Part 1

4. Defining Data Template with XAML

Although we can define data template in code completely, but it is not recommended. In addition we have to write lots of code. Now let's take a look at recommended approach. This time we are going to make XAML file and load that file at run time. This approach will make our work easier and we can use the full power of not only XAML, but also WPF very easily.

Let's first make a XAML file and notice this code is very small as compare to define everything in C#. Here is our XAML file.

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="My Vsualizer" Height="300" Width="400" Background="Blue"
  WindowStartupLocation="CenterScreen">

    <ListBox Name="listBox" Margin="10" HorizontalContentAlignment="Stretch">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Border Background="LightYellow" BorderBrush="Brown" BorderThickness="5">
                    <StackPanel Margin="5">
                        <TextBlock Foreground="Black" FontWeight="Bold" Text="{Binding Path=Type}"/>
                        <TextBlock Foreground="Black" Text="{Binding Path=Value}"/>
                    </StackPanel>
                </Border>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Window>
Now we are going to load the XAML file using XamlReader class. Here is a code to load the XAML file at run time.

Window win = null;

FileStream fs = new FileStream("VisualWindow.xaml", FileMode.Open, FileAccess.Read);

win = (Window)XamlReader.Load(fs);

fs.Close();
If we want to access any control defined in that XAML file then we can use the FindName function. Here is a code to access the ListBox define in the XAML file.

ListBox listBox = win.FindName("listBox") as ListBox;
We also have to include two more namespaces to read the XAML file. These namespaces are System.IO and System.Windows.Markup. Here is a complete C# code to load XAML file runtime and display the integer value in different formats.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.DebuggerVisualizers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.IO;

[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(MyVisualizer.MyVisualizerClass), typeof(VisualizerObjectSource),
Target = typeof(System.Int32),Description = "My Visualizer")]
namespace MyVisualizer
{
    public class MyVisualizerClass : DialogDebuggerVisualizer
    {
        private Int32 obj;

        protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            obj = (Int32)objectProvider.GetObject();

            List<TypeValue> listType = new List<TypeValue>();

            listType.Add(new TypeValue("Decimal", obj.ToString()));
            listType.Add(new TypeValue("Hex", obj.ToString("X")));
            listType.Add(new TypeValue("Octal", DecimalToBase(obj, 8)));
            listType.Add(new TypeValue("Binary", DecimalToBase(obj, 2)));

            Window win = null;

            FileStream fs = new FileStream("VisualWindow.xaml", FileMode.Open, FileAccess.Read);

            win = (Window)XamlReader.Load(fs);

            fs.Close();

            ListBox listBox = win.FindName("listBox") as ListBox;

            listBox.ItemsSource = listType;
            win.ShowDialog();
        }


        // This function is only for debugging purpose
        public static void TestShowVisualizer(object obj)
        {
            VisualizerDevelopmentHost host = new VisualizerDevelopmentHost(obj, typeof(MyVisualizerClass));
            host.ShowVisualizer();
        }


        // Orignally written by Balamurali Balaji
        // Changed little bit to handle the negative sign
        // http://www.codeproject.com/KB/cs/balamurali_balaji.aspx
        private string DecimalToBase(int number, int basenumber)
        {
            string strRetVal = "";
            const int base10 = 10;
            char[] cHexa = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
            int[] result = new int[32];
            int MaxBit = 32;
            bool isNegative = false;

            if (number < 0)
            {
                isNegative = true;
                number *= -1;
            }

            for (; number > 0; number /= basenumber)
            {
                int rem = number % basenumber;
                result[--MaxBit] = rem;
            }

            for (int i = 0; i < result.Length; i++)
            {
                if ((int)result.GetValue(i) >= base10)
                {
                    strRetVal += cHexa[(int)result.GetValue(i) % base10];
                }
                else
                {
                    strRetVal += result.GetValue(i);
                }
            }

            strRetVal = strRetVal.TrimStart(new char[] { '0' });

            if (isNegative)
            {
                strRetVal = strRetVal.Insert(0, "-");
            }

            return strRetVal;
        }
    }

    public class TypeValue
    {
        public TypeValue()
        {
        }

        public TypeValue(String type, String value)
        {
            Type = type;
            Value = value;
        }

        public String Type
        { get; set; }

        public String Value
        { get; set; }
    }
}

We have to copy the VisualWindow.xaml file in the same folder where the dll reside. i.e. in the debugger visualer folder. Now if user clicks on the MyVisualizer menu items either at the code window or watch window then one WPF window will pop and its output would be something like this.

[DebuggerVisualizers_08.gif]

5. Summary

We created one simple Debugger visualizer in WPF with integer data type, but this is a read-only visualizer and supports only one data type. In the comming article I will extend this concept to add support of more than one data type. In addition we will also add the support for editing data.



About the Author

Zeeshan Amjad

C++ Developer at Bechtel Corporation. zamjad.wordpress.com

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • By providing complete access control with granular permissions, deployment flexibility, mapped drive support, and ability to transfer large files, Egnyte provides a more robust, secure and an affordable file sharing solution for the business than Box

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds