5,316,870 members and growing! (15,177 online)
Email Password   helpLost your password?
General Programming » Macros and Add-ins » Tools     Intermediate License: The Code Project Open License (CPOL)

Graphics Debugger Visualizer

By OrlandoCurioso

A debugger visualizer for managed Graphics objects
C# 2.0, C#.NET, .NET 2.0VS2005, Visual Studio, Dev

Posted: 8 Mar 2008
Updated: 8 Mar 2008
Views: 6,279
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
17 votes for this Article.
Popularity: 5.64 Rating: 4.58 out of 5
2 votes, 11.8%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
2 votes, 11.8%
4
13 votes, 76.5%
5
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.

[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

License

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

About the Author

OrlandoCurioso



Location: Germany Germany

Other popular Macros and Add-ins articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 10 of 10 (Total in Forum: 10) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralVS2008 Compatibility notesmemberdzzzen4:36 23 May '08  
GeneralRe: VS2008 Compatibility notesmemberOrlandoCurioso8:34 25 May '08  
AnswerRe: VS2008 Compatibility notesmemberdzzzen10:45 25 May '08  
Generalvery nicemembergeo_m4:53 12 Mar '08  
GeneralVS2008membermarco_ragogna23:40 9 Mar '08  
AnswerRe: VS2008memberOrlandoCurioso2:42 10 Mar '08  
GeneralRe: VS2008memberdzzzen4:40 23 May '08  
GeneralWhat a great idea :)member leppie 11:43 8 Mar '08  
GeneralRe: What a great idea :)memberRoger Alsing14:04 8 Mar '08  
GeneralRe: What a great idea :)memberjomet0:38 10 Mar '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 8 Mar 2008
Editor: Deeksha Shenoy
Copyright 2008 by OrlandoCurioso
Everything else Copyright © CodeProject, 1999-2008
Web10 | Advertise on the Code Project