Graphics Debugger Visualizer






4.97/5 (24 votes)
A debugger visualizer for managed Graphics objects

Introduction
Microsoft made it fairly easy to implement a custom debugger visualizer for Visual Studio and there are numerous samples on the Web.
AFAIG - as far as I can Google - a working visualizer for Graphics
objects has not been published yet. The type handled by the debugger visualizer must be streamable and Graphics
is not serializable by default.
Credits
- MSDN: Visualizers topic
- Taylor Michael explains the problem and outlines the solution
- Brian Scott: Bitmap Debugger Visualizer
- Bob Powell: Zoomable Panel
- Jeremy Jarrell: Custom Debugger Visualizers
Background
The solution consists of using a serializable
wrapper that extracts the Graphics
contents to a bitmap
, which is a serializable
object.
[Serializable]
public sealed class SerializableGraphics : IDisposable
{
private readonly Bitmap bitmap;
public SerializableGraphics(Graphics graphics)
{
if (graphics == null)
{
throw new ArgumentNullException("graphics");
}
FieldInfo fi = graphics.GetType().GetField
("backingImage", BindingFlags.NonPublic | BindingFlags.Instance);
if (fi != null)
{
Bitmap bm = (Bitmap)fi.GetValue(graphics);
if (bm != null)
{
// graphics was derived from image : clone internal bitmap
bitmap = (Bitmap)bm.Clone();
}
else
{
// graphics without backing image : bitblt to new bitmap
Size sz = graphics.VisibleClipBounds.Size.ToSize();
bitmap = new Bitmap(sz.Width, sz.Height, graphics);
drawToBitmap(bitmap, graphics);
}
}
}
public Bitmap Bitmap
{
get { return bitmap; }
}
private static void drawToBitmap(Image bitmap, Graphics graphics)
{
using(Graphics g = Graphics.FromImage(bitmap))
{
IntPtr hdcDst = g.GetHdc();
IntPtr hdcSrc = graphics.GetHdc();
try
{
if (!SafeNativeMethods.BitBlt(
hdcDst, 0, 0, bitmap.Width, bitmap.Height,
hdcSrc, 0, 0, CopyPixelOperation.SourceCopy))
{
throw new Win32Exception();
}
}
finally
{
g.ReleaseHdc(hdcDst);
graphics.ReleaseHdc(hdcSrc);
}
}
}
public void Dispose()
{
if (bitmap != null)
{
bitmap.Dispose();
}
}
}
A derived VisualizerObjectSource
class streams the wrapper on the debuggee side.
internal class GraphicsVisualizerObjectSource : VisualizerObjectSource
{
public override void GetData(object target, System.IO.Stream outgoingData)
{
Graphics data = (Graphics) target;
base.GetData(new SerializableGraphics(data), outgoingData);
}
}
On the debugger side, the data is unwrapped and displayed.
public class GraphicsVisualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService,
IVisualizerObjectProvider objectProvider)
{
using (SerializableGraphics wrapper =
(SerializableGraphics)objectProvider.GetObject())
{
using (BitmapVisualizerForm form = new BitmapVisualizerForm())
{
form.DebugBitmap = wrapper.Bitmap;
windowService.ShowDialog(form);
}
}
}
}
Finally the DebuggerVisualizerAttribute
specifies our types at the assembly level.
[assembly: DebuggerVisualizer(typeof(GraphicsVisualizer),
typeof(GraphicsVisualizerObjectSource),
Target = typeof(Graphics), Description = "Graphics debugger visualizer")]
Options
By default, the bitmap
is shown unscaled and centered to the form. When zoomed, the larger bitmap
width or height is clamped to the form's client area. By resizing the form, the current zoom factor is computed and displayed.
When debugging from a second Visual Studio instance, you can break code operation for 60 seconds. After that, Visual Studio may not be able to continue. Therefore I added a timer function that automatically closes the form after 30 seconds, --- it's time to hit F11/F5.
For convenience the chosen options and the form's last desktop bounds are persisted as a Visual Studio wide setting.
Other Goodies (See Source)
The System.Configuration.CommaDelimitedStringCollection
and my GlobalsHelper
class provide a flexible scheme to persist settings in the EnvDTE.Globals
object. The Globals
cache uses string
name/value pairs and is available at Visual Studio, specific solution or project level.
The FormPlacement
class was originally designed for restoring state of multiple forms using a single string
key in Application Settings.
Improvements
My code provides just a simple readonly visualization, more info of the Graphics
properties could be streamed as a string
or custom class. The native hdc
is serializable
, and a new Graphics
object can be displayed in a propertygrid using the static Graphics.FromHdc
constructor. However I ran into problems of properly releasing the dc
and dropped this in favor of the presented approach. IDialogVisualizerService
allows only to display modal dialogs. It would be nice to display graphics content in a modeless toolwindow, while stepping through code.
Using the Code
Drop the compiled DLL in ..\Microsoft Visual Studio 8\Common7\Packages\Debugger\Visualizers or the user-specific MyDocuments\Visual Studio 2005\Visualizers folder. When debugging and at a breakpoint, the little magnifying glass will appear in the datatip for Graphics
objects.
Points of Interest
The code was tested on Visual Studio 2005/ Windows XP only. Note that, the ProgID
of Visual Studio is hardcoded in the BitmapVisualizerForm.VS_DTE_VERSION
constant.
History
- 8th March, 2008: Article posted