Saturday, 25 February 2012

HowTo: Write a Visual Studio Debugger Visualiser

This post will show how to create a debugger visualiser for visual studio, and use it to display and update a debugged object's data. It is a step-by-step guide, and the resulting code (written in C#) can be found on GitHub. I recommend getting this code to get a feel for how to produce a visualiser.

The object that we're going to visualise has two properties: Colour and IntArray.
public class DemoObject
{
 static Random _random = new Random();

 public DemoObject()
 {
  Colour = Color.DarkOrange;
  //Build a large array that isn't easy to see in the debugger
  IntArray = InitialiseRandomArray(18, 7);
 }

 public int[,] IntArray { get; private set; }
 public Color Colour { get; set; }

 private static int[,] InitialiseRandomArray(int index1, int index2)
 {
  var intArray = new int[index1, index2];

  foreach (var i in Enumerable.Range(0, index1))
  {
   foreach (var j in Enumerable.Range(0, index2))
   {
    intArray[i, j] = _random.Next(0, 255);
   }
  }

  return intArray;
 }
}
As you can see, this is not easily viewed in the debugger:

Our object's IntArray property is a two dimensional array, which we're going to display in a table; Colour will be displayed as the background colour of a picture box.

A debugger visualiser can be a windows control or form; for this demo we'll be using a form. So, create the form, give it a name of "DemoObjectVisualiserForm". Add a textbox - make it multiline, change its name to "arrayContents" - this will be used to display the tabulated contents of the 2 dimensional array. Add a picturebox, call it "colourBox" - this will show the object's colour, rather than the description of the colour shown in the debugger.

As this form is to display an object of type DemoObject, create a constructor that takes an instance of DemoObject as its argument; store this object in a private member variable "_objectToVisualise".

To expose the visualiser API, add a reference to Microsoft.VisualStudio.DebuggerVisualizers:

Add a public class DemoObjectVisualiser, inheriting from DialogDebuggerVisualizer. Implement its Show method:
public class DemoObjectVisualiser : DialogDebuggerVisualizer
{
 protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
 {
  //make sure the object is the correct type
  var objectToVisualise = objectProvider.GetObject() as DemoObject;
  
  //Show the visualiser
  var form = new DemoObjectVisualiserForm(objectToVisualise);
  windowService.ShowDialog(form);
 }
}
Show is the method that's called by the Visual Studio debugger, and takes 2 arguments: IDialogVisualizerService and IVisualizerObjectProvider. IDialogVisualizerService allows the visualiser to be displayed on the UI, and IVisualizerObjectProvider wraps the object to be visualised.

As the object is serialised to be passed between Visual Studio and the DialogDebuggerVisualizer, DemoObject must be decorated with the Serializable attribute unless you plan to implement manual serialisation and use a VisualizerObjectSource.
    [Serializable]
    public class DemoObject
In order to test your visualiser, you can use an instance of the VisualizerDevelopmentHost - this is a proxy of the Visual Studio debugger and is a quick and easy way to fire up your debugger visualiser without running your client code.
This example is a console application, with a reference to Microsoft.VisualStudio.DebuggerVisualizers
using Microsoft.VisualStudio.DebuggerVisualizers;
using VisualiserDemo;

namespace DevelopmentHostDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var objectToVisualise = new DemoObject();
            var host = new VisualizerDevelopmentHost(objectToVisualise, typeof(DemoObjectVisualiser));
            host.ShowVisualizer();
        }
    }
}
So that the debugger knows to give the option of visualising an object using our new visualiser, the object to visualise needs to be decorated with the DebuggerVisualiser attribute:
    [DebuggerVisualizer(typeof(DemoObjectVisualiser), Description="Jonesy's amazing visualiser")]
    [Serializable]
    public class DemoObject
The DemoObject is now able to be visualised in the DemoObjectVisualiserForm; run the client code and break when the DemoObject instance has been created. When you view the instance in the debugger, you'll see a small magnifying glass icon:
Click this, and your object is displayed in the form:

As well as being visualised, the object can be updated via the form. To demonstrate this, add a button to the form and in its Click event handler, hide the form.
Set the Modifiers property of the picture box to Public; this is a rough hack to enable it to be visible outside of the form. Add a Click event handler to the picture box, and add the following code to update the colour:
private void colourBox_Click(object sender, EventArgs e)
{
 //When the colour is clicked, show the dialog to change it
 var colordialog = new ColorDialog();
 var result = colordialog.ShowDialog();
 if (result == System.Windows.Forms.DialogResult.OK)
 {
  colourBox.BackColor = colordialog.Color;
 }
}
The last step to get the colour change reflected in the debugger is to update the object. Add the last few lines here to the Show method in the DemoObjectVisualiser object:
public class DemoObjectVisualiser : DialogDebuggerVisualizer
{
 protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
 {
  //make sure the object is the correct type
  var objectToVisualise = objectProvider.GetObject() as DemoObject;
  
  //Show the visualiser
  var form = new DemoObjectVisualiserForm(objectToVisualise);
  windowService.ShowDialog(form);
  
  //If the object is replaceable, update the colour
  if (objectProvider.IsObjectReplaceable)
  {
   objectToVisualise.Colour = form.colourBox.BackColor;
   objectProvider.ReplaceObject(objectToVisualise);
  }
 }
}
This will update the debugged object with the new colour, when the save button is clicked:

No comments:

Post a Comment