Click here to Skip to main content
15,881,380 members
Articles / Programming Languages / C#
Article

Graphics Debugger Visualizer

Rate me:
Please Sign up or sign in to vote.
4.97/5 (26 votes)
8 Mar 2008CPOL2 min read 80.9K   1.9K   68   14
A debugger visualizer for managed Graphics objects
AVG_certification

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

Background

The solution consists of using a serializable wrapper that extracts the Graphics contents to a bitmap, which is a serializable object.

C#
[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.

C#
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.

C#
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.

C#
[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

License

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


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionexcellent! Pin
Southmountain31-May-22 6:34
Southmountain31-May-22 6:34 
GeneralThanks Pin
Saeed.39415-Aug-10 0:54
Saeed.39415-Aug-10 0:54 
GeneralVS2008 Compatibility notes Pin
dzzzen23-May-08 3:36
dzzzen23-May-08 3:36 
GeneralRe: VS2008 Compatibility notes Pin
OrlandoCurioso25-May-08 7:34
OrlandoCurioso25-May-08 7:34 
AnswerRe: VS2008 Compatibility notes Pin
dzzzen25-May-08 9:45
dzzzen25-May-08 9:45 
GeneralRe: VS2008 Compatibility notes Pin
Hardy Wang8-Feb-09 3:29
Hardy Wang8-Feb-09 3:29 
AnswerRe: VS2008 Compatibility notes Pin
Jim Strawn4-Dec-09 9:38
Jim Strawn4-Dec-09 9:38 
Generalvery nice Pin
geo_m12-Mar-08 3:53
geo_m12-Mar-08 3:53 
GeneralVS2008 Pin
akerd9-Mar-08 22:40
akerd9-Mar-08 22:40 
AnswerRe: VS2008 Pin
OrlandoCurioso10-Mar-08 1:42
OrlandoCurioso10-Mar-08 1:42 
GeneralRe: VS2008 Pin
dzzzen23-May-08 3:40
dzzzen23-May-08 3:40 
QuestionWhat a great idea :) Pin
leppie8-Mar-08 10:43
leppie8-Mar-08 10:43 
AnswerRe: What a great idea :) Pin
Roger Alsing8-Mar-08 13:04
Roger Alsing8-Mar-08 13:04 
AnswerRe: What a great idea :) Pin
jomet9-Mar-08 23:38
jomet9-Mar-08 23:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.