Posted by: Zeeshan Amjad | October 7, 2009

Creating Debugger Visualizer in WPF: Part 1


We have seen lots of good examples of how to create Debugger visualizer for Visual Studio. But one this is common among all of them that all of them are written in windows form. I got one question on Microsoft form that is it possible to make debugger visualizer in WPF and decided to do one experiment. After playing with it little bit i realized that yes we can make debugger visualizer even in WPF. And this opens a whole new door for for me, because now we can display data in much more sophisticated way using the modern technology.

Lets 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. There is only one method to overload in this class and here is the signature of that method.

  1: protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)

Now here is a little catch. In traditional windows form debugger, we are going to call ShowDialog method of IDialogVisualizerService interface that will internally call the Windows form. But if we want to make a WPF based visualizer then we have to create window object and call it ourselves.

In its simplest way it is something like this

  1: protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
  2: {
  3:     // set the attributes of WPF window
  4:     Window win = new Window();
  5:     win.Title = "My Visualizer";
  6:     win.Width = 400;
  7:     win.Height = 300;
  8:     win.Background = Brushes.Blue;
  9:     win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
 10: 
 11:     win.ShowDialog();
 12: }
 13: 

This will display the WPF visualizer window with blue background. Lets 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.

  1: [assembly: System.Diagnostics.DebuggerVisualizer(
  2: typeof(MyVisualizer.MyVisualizerClass), typeof(VisualizerObjectSource),
  3: Target = typeof(System.Int32),Description = "My Visualizer")]
  4: namespace MyVisualizer
  5: {
  6: }

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 over ride GetData method.

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 proffered way is to write XAML and load that XAML programmatically. The other approach is to make everything programmatically.

In this article i am going to use coding approach and define data template programmatically. In the next article i will do the same thing using the XAML file.

Our target is to display the value of integer variable in 4 different format. We will display integer in decimal, hex decimal, octal and binary format. Lets first make a small function for base conversion. I picked this function from code project originally written by “Balamurali Balaji” and modified it little bit to handle the negative number. Here is modified version of base convertor.

  1: // Orignally written by Balamurali Balaji
  2: // Changed little bit to handle the negative sign
  3: // http://www.codeproject.com/KB/cs/balamurali_balaji.aspx
  4: private string DecimalToBase(int number, int basenumber)
  5: {
  6:     string strRetVal = "";
  7:     const int base10 = 10;
  8:     char[] cHexa = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
  9:     int[] result = new int[32];
 10:     int MaxBit = 32;
 11:     bool isNegative = false;
 12: 
 13:     if (number < 0)
 14:     {
 15:         isNegative = true;
 16:         number *= -1;
 17:     }
 18: 
 19:     for (; number > 0; number /= basenumber)
 20:     {
 21:         int rem = number % basenumber;
 22:         result[--MaxBit] = rem;
 23:     }
 24: 
 25:     for (int i = 0; i < result.Length; i++)
 26:     {
 27:         if ((int)result.GetValue(i) >= base10)
 28:         {
 29:             strRetVal += cHexa[(int)result.GetValue(i) % base10];
 30:         }
 31:         else
 32:         {
 33:             strRetVal += result.GetValue(i);
 34:         }
 35:     }
 36: 
 37:     strRetVal = strRetVal.TrimStart(new char[] { '0' });
 38: 
 39:     if (isNegative)
 40:     {
 41:         strRetVal = strRetVal.Insert(0, "-");
 42:     }
 43: 
 44:     return strRetVal;
 45: }
 46: 

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.

  1: public class TypeValue
  2: {
  3:     public TypeValue()
  4:     {
  5:     }
  6: 
  7:     public TypeValue(String type, String value)
  8:     {
  9:         Type = type;
 10:         Value = value;
 11:     }
 12: 
 13:     public String Type
 14:     { get; set; }
 15: 
 16:     public String Value
 17:     { get; set; }
 18: }
 19: 

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

  1: List<TypeValue> listType = new List<TypeValue>();
  2: 
  3: Int32 obj = (Int32)objectProvider.GetObject();            
  4: 
  5: listType.Add(new TypeValue("Decimal", obj.ToString()));
  6: listType.Add(new TypeValue("Hex", obj.ToString("X")));
  7: listType.Add(new TypeValue("Octal", DecimalToBase(obj, 8)));
  8: listType.Add(new TypeValue("Binary", DecimalToBase(obj, 2)));
  9: 
 10: listBox.ItemsSource = listType;
 11: 

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.

  1: // This function is only for debugging purpose
  2: public static void TestShowVisualizer(object obj)
  3: {
  4:     VisualizerDevelopmentHost host = new VisualizerDevelopmentHost(obj, typeof(MyVisualizerClass));
  5:     host.ShowVisualizer();
  6: }
  7: 

 

Here is the complete code of this visualizer.

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Linq;
  4: using System.Text;
  5: using Microsoft.VisualStudio.DebuggerVisualizers;
  6: using System.Windows;
  7: using System.Windows.Controls;
  8: using System.Windows.Media;
  9: using System.Windows.Data;
 10: 
 11: [assembly: System.Diagnostics.DebuggerVisualizer(
 12: typeof(MyVisualizer.MyVisualizerClass), typeof(VisualizerObjectSource),
 13: Target = typeof(System.Int32),Description = "My Visualizer")]
 14: namespace MyVisualizer
 15: {
 16:     public class MyVisualizerClass : DialogDebuggerVisualizer
 17:     {
 18:         protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
 19:         {
 20:             // set the attributes of WPF window
 21:             Window win = new Window();
 22:             win.Title = "My Visualizer";
 23:             win.Width = 400;
 24:             win.Height = 300;
 25:             win.Background = Brushes.Blue;
 26:             win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
 27: 
 28:             ListBox listBox = new ListBox();
 29:             listBox.Margin = new Thickness(10);
 30:             listBox.HorizontalContentAlignment = HorizontalAlignment.Stretch;
 31: 
 32:             DataTemplate dt = new DataTemplate();
 33: 
 34:             Binding bindingType = new Binding();
 35:             bindingType.Path = new PropertyPath("Type");
 36: 
 37:             Binding bindnigValue = new Binding();
 38:             bindnigValue.Path = new PropertyPath("Value");
 39: 
 40:             FrameworkElementFactory textType = new FrameworkElementFactory(typeof(TextBlock));
 41:             textType.SetBinding(TextBlock.TextProperty, bindingType);
 42:             textType.SetValue(Control.ForegroundProperty, Brushes.Blue);
 43:             textType.SetValue(Control.FontWeightProperty, FontWeights.Bold);
 44: 
 45:             FrameworkElementFactory textValue = new FrameworkElementFactory(typeof(TextBlock));
 46:             textValue.SetBinding(TextBlock.TextProperty, bindnigValue);
 47:             textValue.SetValue(Control.ForegroundProperty, Brushes.Black);
 48: 
 49:             FrameworkElementFactory stack = new FrameworkElementFactory(typeof(StackPanel));
 50:             stack.SetValue(Control.MarginProperty, new Thickness(5));
 51:             stack.AppendChild(textType);
 52:             stack.AppendChild(textValue);
 53: 
 54:             FrameworkElementFactory border = new FrameworkElementFactory(typeof(Border));
 55:             border.SetValue(Control.BackgroundProperty, Brushes.LightYellow);
 56:             border.SetValue(Control.BorderBrushProperty, Brushes.Brown);
 57:             border.SetValue(Control.BorderThicknessProperty, new Thickness(3));
 58:             border.SetValue(Control.MarginProperty, new Thickness(5));
 59:             border.AppendChild(stack);
 60: 
 61:             dt.VisualTree = border;
 62:             listBox.ItemTemplate = dt;
 63: 
 64:             List<TypeValue> listType = new List<TypeValue>();
 65: 
 66:             Int32 obj = (Int32)objectProvider.GetObject();            
 67: 
 68:             listType.Add(new TypeValue("Decimal", obj.ToString()));
 69:             listType.Add(new TypeValue("Hex", obj.ToString("X")));
 70:             listType.Add(new TypeValue("Octal", DecimalToBase(obj, 8)));
 71:             listType.Add(new TypeValue("Binary", DecimalToBase(obj, 2)));
 72: 
 73:             listBox.ItemsSource = listType;
 74: 
 75:             win.Content = listBox;
 76:             win.ShowDialog();
 77:         }
 78: 
 79:         // This function is only for debugging purpose
 80:         public static void TestShowVisualizer(object obj)
 81:         {
 82:             VisualizerDevelopmentHost host = new VisualizerDevelopmentHost(obj, typeof(MyVisualizerClass));
 83:             host.ShowVisualizer();
 84:         }
 85: 
 86:         // Orignally written by Balamurali Balaji
 87:         // Changed little bit to handle the negative sign
 88:         // http://www.codeproject.com/KB/cs/balamurali_balaji.aspx
 89:         private string DecimalToBase(int number, int basenumber)
 90:         {
 91:             string strRetVal = "";
 92:             const int base10 = 10;
 93:             char[] cHexa = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
 94:             int[] result = new int[32];
 95:             int MaxBit = 32;
 96:             bool isNegative = false;
 97: 
 98:             if (number < 0)
 99:             {
100:                 isNegative = true;
101:                 number *= -1;
102:             }
103: 
104:             for (; number > 0; number /= basenumber)
105:             {
106:                 int rem = number % basenumber;
107:                 result[--MaxBit] = rem;
108:             }
109: 
110:             for (int i = 0; i < result.Length; i++)
111:             {
112:                 if ((int)result.GetValue(i) >= base10)
113:                 {
114:                     strRetVal += cHexa[(int)result.GetValue(i) % base10];
115:                 }
116:                 else
117:                 {
118:                     strRetVal += result.GetValue(i);
119:                 }
120:             }
121: 
122:             strRetVal = strRetVal.TrimStart(new char[] { '0' });
123: 
124:             if (isNegative)
125:             {
126:                 strRetVal = strRetVal.Insert(0, "-");
127:             }
128: 
129:             return strRetVal;
130:         }
131:     }
132: 
133:     public class TypeValue
134:     {
135:         public TypeValue()
136:         {
137:         }
138: 
139:         public TypeValue(String type, String value)
140:         {
141:             Type = type;
142:             Value = value;
143:         }
144: 
145:         public String Type
146:         { get; set; }
147: 
148:         public String Value
149:         { get; set; }
150:     }
151: }
152: 

Let’s make a small program to test this visualizer.

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Linq;
  4: using System.Text;
  5: using MyVisualizer;
  6: 
  7: namespace MyVisualizerClient
  8: {
  9:     class Program
 10:     {
 11:         [STAThread]
 12:         static void Main(string[] args)
 13:         {
 14:             int iValue = -100;
 15: 
 16:             MyVisualizerClass.TestShowVisualizer(iValue);
 17: 
 18:             Console.WriteLine(iValue);
 19:         }
 20:     }
 21: }
 22: 

Wehn 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 will the “My Visualizer” option in context menu.

Visualizer_01

It is also available at watch window.

Visualizer_02

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.

Visualizer_03

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: