Writing a More Robust Debugger Visualizer in WPF

1. Introduction

In the first article, Writing a Debugger Visualizer in WPF: Part 1, 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 discussion one visualizer was created using XAML file. Now let's move forward and improve the functionality of the visualizer. The previous visualizer was a read-only visualizer in taht it could only view the data in it. Now we are going to make a visualizer that is interactive and thus has a feature to change the value of a variable.

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

We added one text box in our WPF window to enter data. Here is a piece of code for the new text box.

<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 the new text box is "txtValue". We use the same FindWindow technique, which we used in the previous article, to get the value of this text box 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 the complete XAML code for 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 you click on the MyVisualizer either on the Watch window or on the context menu that appears when you clicks on the integer variable, then you will see the following window:

Writing a More Robust Debugger Visualizer in WPF

3. Support more than one Data type

Until now, the visualizer was limited to only one data type, specifically an integer data type, but any good visualizer is not limited to only one data type. Let's extend our visualizer a little bit more and handle more so it can handle more than one data type. In this case we are going to add the string data type support in our visualizer. This time we are going to display the string in upper case and lower case as well as show length and base 64 encoded format.

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

[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
{

}

This visualizer supports two data type integer and string. To simplify, we created different classes to handle string and integer data type. Our string class is almost similar to integer class display information in different format. For simplicity we use the same window class for both integer and string type, 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 complete C# code of 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 string data type, then its output will be something like this:

[DebuggerVisualizers_11.gif]

4. Non Serializerable Class

Until now we studied how to created a debugger visualizer to support multiple data types in WPF. But until now we were using the serializable data types. What will be the situation if we want to create a debugger visualizer for non serializable data type, such as Color, Brush etc. Today we are going to extend our visualizer to support non serializable data type, specifically, 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 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 3 data types, integer, string and Color.

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

public class ColorSource : VisualizerObjectSource
{

}

Here is a class diagram of VisualizerObjectSource class.

[DebuggerVisualizers_12.gif]

For every non serializable class we have to do searlization ourselves and that's exactly the reason to inherit class from visualizerObjectSource class. Here we override GetData function and perform 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 one namespace for BinaryFormatter.

using System.Runtime.Serialization.Formatters.Binary;

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

[DebuggerVisualizers_13.gif]

Here is a wrapper class of Color which implement this interface.

[Serializable]
public class MyColor : ISerializable
{

}

We can't create object of this class, because it hasn't override the GetObjectData method. In addition this class doesn't do anything with Color type. Here is 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 ToString method to display the different component of color.

Once we are done with it then rest of the stuff is quite easy. We created different XAML file to display the name of color and display one rounded border in that color. Here is complete XAML file for this project for 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 XAML file to display 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 of this project is very long, so it is not shown here, but it can be downloaded from the attach project. Here is a output of the visualizer, when see the value of any color type variable.

[DebuggerVisualizers_14.gif]



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

  • On-Demand Webcast APIs can be a great source of competitive advantage. The practice of exposing backend services as APIs has become pervasive, however their use varies widely across companies and industries. Some companies leverage APIs to create internal, operational and development efficiencies, while others use them to drive ancillary revenue channels. Many companies successfully support both public and private programs from the same API by varying levels of access to different constituents. Nearly all …

  • On-demand Event Event Date: December 18, 2014 The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this webcast and join industry experts as …

Most Popular Programming Stories

More for Developers

RSS Feeds