Click here to Skip to main content
Click here to Skip to main content

Writing a Debugger Visualizer in WPF: Part 2

, 4 Jun 2010
Rate this:
Please Sign up or sign in to vote.
This article describes writing a debugger visualizer in WPF for multiple data types.

1. Introduction

In the first article, we discussed how to create a basic debugger visualizer in WPF. Our visualizer was read only and supported only one data type. Now, let’s extend this concept and try to improve it.

2. Editable visualizer

In the previous article, we created a visualizer using a XAML file. Now, let’s move forward and improve the functionality of our visualizer. Our previous visualizer was a read only visualizer, i.e., we could only view the data in it. Now we are going to make a visualizer that is interactive, i.e., it has a feature to change the value of a variable.

The basic concept behind this is to use the ReplaceObject function of the IVisualizerObjectProvider interface. Here is a class diagram of this interface.

We add a TextBox in our WPF window to enter data. Here is a piece of code of that new TextBox:

<Border Margin="10" Background="AliceBlue" BorderBrush="Navy" 
         BorderThickness="5" CornerRadius="5">
    <StackPanel>
        <TextBlock Margin="5">Enter new Value</TextBlock>
        <TextBox Name="txtValue" Margin="5"/>
    </StackPanel>
</Border>

The name of our new TextBox is txtValue. We use the same FindWindow technique which we used earlier, to get the value of this TextBox, and then call ReplaceObject. After that, we close the window.

TextBox text = win.FindName("txtValue") as TextBox;

Int32 newValue = Convert.ToInt32(text.Text);

if (objProvider.IsObjectReplaceable)
{
    objProvider.ReplaceObject(newValue);
}

win.Close();

Here is a complete XAML code of this program:

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="My Vsualizer" Height="400" Width="400" Background="Wheat"
  WindowStartupLocation="CenterScreen">
    <StackPanel>
        <Border Margin="10" Background="AliceBlue" 
                   BorderBrush="Navy" BorderThickness="5" CornerRadius="5">
            <StackPanel>
                <TextBlock Margin="5">Enter new Value</TextBlock>
                <TextBox Name="txtValue" Margin="5"/>
            </StackPanel>
        </Border>

        <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>
        <Button Name="btnOK" Margin="10" Width="75">OK</Button>
    </StackPanel>
</Window>

Here is the complete C# code for this project:

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;
        private Window win;
        private IVisualizerObjectProvider objProvider;

        protected override void Show(IDialogVisualizerService windowService, 
                           IVisualizerObjectProvider objectProvider)
        {
            objProvider = objectProvider;
            obj = (Int32)objProvider.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)));

            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;

            Button buttonOK = win.FindName("btnOK") as Button;

            buttonOK.Click += new RoutedEventHandler(buttonOK_Click);

            win.ShowDialog();
        }

        void buttonOK_Click(object sender, RoutedEventArgs e)
        {
            TextBox text = win.FindName("txtValue") as TextBox;

            Int32 newValue = Convert.ToInt32(text.Text);

            if (objProvider.IsObjectReplaceable)
            {
                objProvider.ReplaceObject(newValue);
            }

            win.Close();
        }

        // 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 sig
        // 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 when we click on the MyVisualizer, either a Watch window or a context menu appears. If we click on the integer variable name, then we will see the following window:

3. Support for more than one data type

Till now, our visualizer was limited to only one data type, the integer data type. But any good visualizer supports multiple data types. Let’s extend our visualizer a little bit more and handle more than one data type with it. In this case, we are going to add string data type support in our visualizer. This time, we are going to display a string in upper case, lower case, its length, and in base 64 encoded format.

We define the attributes in our namespace which defines the supported data types by the visualizer. If we want to make our visualizer support more than one data type, then we have to define this attribute more than once with different data types.

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

}

Our visualizer now supports two data types: integer and string. For simplicity, we create different classes to handle string and integer data types. Our string class is almost similar to the integer class, except it displays information in different formats. Just for simplicity, we use the same window class for both integer and string types, but this is not a requirement.

System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
List<TypeValue> listType = new List<TypeValue>();

listType.Add(new TypeValue("String", obj));
listType.Add(new TypeValue("Upper", obj.ToUpper()));
listType.Add(new TypeValue("Lower", obj.ToLower()));
listType.Add(new TypeValue("Length", obj.Length.ToString()));
listType.Add(new TypeValue("Base 64 Encoding", 
             Convert.ToBase64String(encoding.GetBytes(obj))));

And here is the complete C# code for our project:

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.MyInt32Class), typeof(VisualizerObjectSource),
Target = typeof(System.Int32),Description = "My Visualizer")]
[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(MyVisualizer.MyStringClass), typeof(VisualizerObjectSource),
Target = typeof(System.String), Description = "My Visualizer")]
namespace MyVisualizer
{
    public class MyStringClass : DialogDebuggerVisualizer
    {
        private Window win;
        private String obj;
        private IVisualizerObjectProvider objProvider;

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

            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            List<TypeValue> listType = new List<TypeValue>();

            listType.Add(new TypeValue("String", obj));
            listType.Add(new TypeValue("Upper", obj.ToUpper()));
            listType.Add(new TypeValue("Lower", obj.ToLower()));
            listType.Add(new TypeValue("Length", obj.Length.ToString()));
            listType.Add(new TypeValue("Base 64 Encoding", 
                         Convert.ToBase64String(encoding.GetBytes(obj))));

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

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

            fs.Close();

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

            listBox.ItemsSource = listType;

            Button buttonOK = win.FindName("btnOK") as Button;

            buttonOK.Click += new RoutedEventHandler(buttonOK_Click);

            win.ShowDialog();
        }

        void buttonOK_Click(object sender, RoutedEventArgs e)
        {
            TextBox text = win.FindName("txtValue") as TextBox;

            String newValue = text.Text;

            if (objProvider.IsObjectReplaceable)
            {
                objProvider.ReplaceObject(newValue);
            }

            win.Close();
        }

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

    public class MyInt32Class : DialogDebuggerVisualizer
    {
        private Int32 obj;
        private Window win;
        private IVisualizerObjectProvider objProvider;

        protected override void Show(IDialogVisualizerService windowService, 
                           IVisualizerObjectProvider objectProvider)
        {
            objProvider = objectProvider;
            obj = (Int32)objProvider.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)));

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

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

            fs.Close();

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

            listBox.ItemsSource = listType;

            Button buttonOK = win.FindName("btnOK") as Button;

            buttonOK.Click += new RoutedEventHandler(buttonOK_Click);

            win.ShowDialog();
        }

        void buttonOK_Click(object sender, RoutedEventArgs e)
        {
            TextBox text = win.FindName("txtValue") as TextBox;

            Int32 newValue = Convert.ToInt32(text.Text);

            if (objProvider.IsObjectReplaceable)
            {
                objProvider.ReplaceObject(newValue);
            }

            win.Close();
        }

        // This function is only for debugging purpose
        public static void TestShowVisualizer(object obj)
        {
            VisualizerDevelopmentHost host = 
              new VisualizerDevelopmentHost(obj, typeof(MyInt32Class));
            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 if we use this visualizer for the string data type, then its output will be something like this:

4. Non-serializerable class

Till now, we studied how to create a debugger visualizer to support multiple data types in WPF. But we were using serializable data types for this. What will be the situation if we want to create a debugger visualizer for a non-serializable data type, such as Color, Brush, etc.? Now, we are going to extend our visualizer to support non-serializable data types like Color.

The first thing we have to do is to add the attribute in our namespace for the Color type. For simplicity, we create another class name MyColorClass to handle the Color type. Here is the declaration of the namespace:

[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(MyVisualizer.MyInt32Class), typeof(VisualizerObjectSource),
Target = typeof(Int32),Description = "My Visualizer")]
[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(MyVisualizer.MyStringClass), typeof(VisualizerObjectSource),
Target = typeof(String), Description = "My Visualizer")]
[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(MyVisualizer.MyColorClass), typeof(MyVisualizer.ColorSource),
Target = typeof(Color), Description = "My Visualizer")]
namespace MyVisualizer
{

}

Now our visualizer supports three data types: integer, string, and color.

In addition, there is one more difference. Instead of using VisualizerObjectSource, we are going to use the ColorSource class which is in fact inherited by the VisualizerObjectSource class.

public class ColorSource : VisualizerObjectSource
{
}

Here is a class diagram of the VisualizerObjectSource class:

For every non-serializable class, we have to do the serialization ourselves, and that’s exactly the reason to inherit the class from the VisualizerObjectSource class. Here, we override the GetData function and perform the serialization of the object.

public class ColorSource : VisualizerObjectSource
{
    public override void GetData(object target, Stream outgoingData)
    {
        if (target != null && target is Color)
        {
            Color color = (Color)target;
            MyColor obj = new MyColor(color);

            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(outgoingData, obj);
        }
    }
}

We have to include a namespace for the BinaryFormatter.

using System.Runtime.Serialization.Formatters.Binary;

But the Color class is non-serializable and whenever we are going to perform serialization on it, we will get a SerializationException. The solution is to create a wrapper class of that data type, implement the ISerializable interface, and the define the Serializable attribute for our class. This interface has only one method to overload, named GetObjectData. Here is a class diagram of the ISerializable interface:

Here is a wrapper class of Color which implements this interface:

[Serializable]
public class MyColor : ISerializable
{
}

We can’t create an object of this class, because it hasn’t overridden the GetObjectData method. In addition, this class doesn’t do anything with the Color type. Here is a complete implementation of this class:

[Serializable]
public class MyColor : ISerializable
{
    private Color _color;

    public MyColor()
    {
    }

    public MyColor(Color color)
    {
        this.color = color;
    }

    public Color color
    {
        get { return _color; }
        set { _color = value; }
    }

    protected MyColor(SerializationInfo info, StreamingContext context)
    {
        _color.A = info.GetByte("A");
        _color.R = info.GetByte("R");
        _color.G = info.GetByte("G");
        _color.B = info.GetByte("B");
    }

    [SecurityPermissionAttribute(SecurityAction.Demand,
    SerializationFormatter = true)]
    public virtual void GetObjectData(SerializationInfo info, 
                                      StreamingContext context)
    {
        info.AddValue("A", color.A);
        info.AddValue("R", color.R);
        info.AddValue("G", color.G);
        info.AddValue("B", color.B);
    }

    public override string ToString()
    {
        return String.Format("({0}, {1}, {2}, {3})",
            color.A.ToString(),
            color.R.ToString(),
            color.G.ToString(),
            color.B.ToString());
    }
}

We also override the ToString method to display the different components of the color.

Once we are done with it, then the rest of the stuff is quite easy. We created different XAML files to display the name of the color and display a rounded border in that color. Here is the complete XAML file for this project for the Color type:

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="My Vsualizer" Height="400" Width="400" Background="Wheat"
  WindowStartupLocation="CenterScreen">
    <StackPanel>
        <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>
        <Border Name="colorBorder" Margin="10" Height="250" 
                 BorderBrush="Black" BorderThickness="1" CornerRadius="10" />
    </StackPanel>
</Window>

And, here is the XAML file to display the integer and string types:

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="My Vsualizer" Height="400" Width="400" Background="Wheat"
  WindowStartupLocation="CenterScreen">
    <StackPanel>
        <Border Margin="10" Background="AliceBlue" 
                   BorderBrush="Navy" BorderThickness="5" CornerRadius="5">
            <StackPanel>
                <TextBlock Margin="5">Enter new Value</TextBlock>
                <TextBox Name="txtValue" Margin="5"/>
            </StackPanel>
        </Border>

        <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.ItemTemplate7gt;
        </ListBox>
        <Button Name="btnOK" Margin="10" Width="75"7>OK7lt;/Button>>
    </StackPanel>
</Window>

The complete source code for this project is very long, so it is not shown here, but it can be downloaded from the link above. Here is the output of the visualizer, where you can see the value of a Color type variable:

License

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

About the Author

Zeeshan Amjad
Software Developer (Senior) Bloomberg LP
United States United States
Working as a Sr C++ Developer at Bloomberg LP

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web03 | 2.8.140709.1 | Last Updated 4 Jun 2010
Article Copyright 2010 by Zeeshan Amjad
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid