Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ADCollectionsVisualizer

0.00/5 (No votes)
14 Sep 2008 1  
A VS2005 visualizer to help view the content of a wide variety of collection-type objects

Visual Studio 2005

Visual Studio 2008

General

Screenshot - ADCollectionsVisualizer1.png

Introduction

ADCollectionsVisualizer is a Visualizer add-in for Visual Studio 2005 and Visual Studio 2008 which allows a wide range of collection and dictionary-style objects to be easily examined.

Background

One of the fantastic new features of the Visual Studio 2005/2008 IDE is the ability to use Visualizers. These are utility windows that attach themselves to various object types within the Visual Studio environment. When you hover the mouse over a variable of one of the supported object types, a magnifying glass icon appears within the popup tooltip, and clicking it opens the Visualizer window for that particular variable, allowing the variable content to be viewed much more easily.

Several Visualizers are included with Visual Studio by default, to allow text to be viewed (as plain text, HTML or XML) and to allow DataTables to be examined more closely. The IDE allows further Visualizers to be written, however, such as ADCollectionsVisualizer.

ADCollectionsVisualizer allows a whole variety of collection and dictionary classes (and objects for all classes that inherit from these classes) to be viewed in a grid, and also copied to the clipboard and saved to disk if required.

Using the Code

The visualizer can be installed by either running the installation executable, or by unzipping the DLL and placing it into your My Documents\Visual Studio 2005\Visualizers or My Documents\Visual Studio 2008\Visualizers directory, as appropriate.

Note that if you are compiling the visualizer from its source code, you can use a Build Event to automatically copy the visualizer to the installation directory. You can find this by double-clicking the My Project icon, then selecting the Compile tab and clicking the Build Events button (such a build event is configured within the provided project file — you will most likely need to change the path to wherever your My Documents directory is located).

After this has been done, simply hover the mouse over a collection object within the IDE whilst in break mode. A small magnifying glass icon should appear; click on this to open the visualizer.

Screenshot - ADCollectionsVisualizer2.png

The visualizer supports a wide range of collection-type objects, including:

  • System.Collections classes
    • System.Collections.ArrayList
    • System.Collections.BitArray
    • System.Collections.HashTable
    • System.Collections.Queue
    • System.Collections.SortedList
    • System.Collections.Stack
  • System.Collections.Specialized classes
    • System.Collections.Specialized.HybridDictionary
    • System.Collections.Specialized.ListDictionary
    • System.Collections.Specialized.NameValueCollection
    • System.Collections.Specialized.OrderedDictionary
    • System.Collections.Specialized.StringCollection
    • System.Collections.Specialized.StringDictionary
    • All classes derived from System.Collections.CollectionBase
    • All classes derived from System.Collections.Specialized.NameObjectCollectionBase
  • System.Collections.Generic classes
    • System.Collections.Generic.Dictionary
    • System.Collections.Generic.List
    • System.Collections.Generic.LinkedList
    • System.Collections.Generic.Queue
    • System.Collections.Generic.SortedDictionary
    • System.Collections.Generic.SortedList
    • System.Collections.Generic.Stack
  • IIS classes, as used by
    • System.Web.HttpRequest.Cookies
    • System.Web.HttpRequest.Files
    • System.Web.HttpRequest.Form
    • System.Web.HttpRequest.Headers
    • System.Web.HttpRequest.Params
    • System.Web.HttpRequest.QueryString
    • System.Web.HttpRequest.ServerVariables
    • System.Web.HttpResponse.Cookies
  • VB6-compatible collections
    • Microsoft.VisualBasic.Collection (values only — keys cannot be retrieved from this type of collection)

The code itself consists of three main classes:

  • CollectionVisualizer
  • ObjectSource 
  • PreviewForm

CollectionVisualizer contains the visualizer itself, along with all of the DebuggerVisualizer assembly attributes. There are quite a lot of these, to handle all of the different types of collection and dictionary classes that the visualizer supports:

<Assembly: DebuggerVisualizer(GetType(CollectionVisualizer),
    GetType(ObjectSource), Target:=GetType(Collections.ArrayList),
    Description:="Collection Visualizer")>
<Assembly: DebuggerVisualizer(GetType(CollectionVisualizer),
    GetType(ObjectSource), Target:=GetType(Collections.BitArray),
    Description:="Collection Visualizer")>
<Assembly: DebuggerVisualizer(GetType(CollectionVisualizer),
    GetType(ObjectSource), Target:=GetType(Collections.Hashtable),
    Description:="Collection Visualizer")>
[...etc...]

Notice that two of the project classes are referenced within every one of the attributes: the first is the CollectionVisualizer class, which is responsible for doing the work, and the second is the ObjectSource which packages up all of the data into a form which can be serialized in order for the visualizer to access it. Further details on this are below. The third class reference within each of the attributes points to the type of class for which the visualizer is to function. This changes for each attribute, and the repeated attributes allow all of the supported classes to be handled by the same visualizer class.

If you wish to know more about how to write visualizers, you will find other articles within The Code Project that explain this in great detail.

The latest release now also supports Generic collection objects. These are specified in a slightly different manner within the assembly attributes:

<Assembly: DebuggerVisualizer(GetType(CollectionVisualizer),
		GetType(ObjectSource), 
                  Target:=GetType(collections.Generic.Dictionary(Of ,)),
		Description:="Collection Visualizer")>
<Assembly: DebuggerVisualizer(GetType(CollectionVisualizer),
		GetType(ObjectSource), Target:=GetType(collections.Generic.List(Of )),
		Description:="Collection Visualizer")>
[...etc...]

The generic class type is specified using the class name followed by the Of keyword in parentheses, but with no class type specified. Where multiple class types are required, a comma is used to separate the types but once again no types are specified. This allows the visualizer to work with the generic classes regardless of the types of data that are being stored within them.

The visualizer uses a custom VisualizerObjectSource in order to package up the data to be visualized. The IDE serializes the data that is to be visualized, and some of the collection types supported by ADCollectionsVisualizer are not able to be serialized. To work around this, the ObjectSource class extracts all of the data from the collection and puts it into an ADO.NET DataTable, representing each object's value as a simple String. This DataTable is then serialized and passed to the visualizer. The ObjectSource class is responsible for recognising all of the different collection types that are supported and decoding their data into the DataTable. For the most part this is straightforward, but some of the types (in particular objects that derive from NameObjectCollectionBase) are a bit more tricky to access than they might have been.

Finally the PreviewForm displays the data to be visualized on the screen. It uses a DataGrid for its presentation, and also offers the ability to copy the collection data to the clipboard or save it to disk in CSV, text or XML formats.

Points of Interest

There are a couple of areas of code that might prove useful or interesting to anyone that hasn't encountered them before — they certainly had me guessing for a while.

The first is the code that accesses the values within an object derived from NameObjectCollectionBase. I could see that there were two protected members within the class called BaseGetAllKeys and BaseGetAllValues — obviously these would be exactly what I need to extract all the data from the collection. However, as they are protected, my code doesn't have any direct access to them.

I used a rather hacky piece of reflection to gain access to these functions. I definitely would not recommend using techniques such as this in production code, but in the context of what the visualizer is trying to do I think they are an acceptable technique. Without them, I would be unable to support collections based upon this class.

The second thing that took some experimentation and research to get right is the customisation of the DataGrid. By default the grid allows the values to be edited (which is not something that the visualizer supports at present) and highlights the selected cell with a gray background. I created a class named CustomGridTextBoxColumn which derives from .NET's DataGridTextBoxColumn and added some code to prevent editing and to properly control the foreground and background colours of the cells. Despite being a bit of a pain to set up, it works very well and provides exactly the functionality I was looking for.

The code that implements the DataGridTextBoxColumn is as follows:

'''
''' A custom DataGridTextBoxColumn that provides some changed functionality,
''' required by the visualizer
'''
Public Class CustomGridTextBoxColumn
    Inherits DataGridTextBoxColumn
    '''
    ''' When the grid attempts to go into Edit mode, this method will return
    ''' without calling into the base class, and so prevents the edit from
    ''' starting.
    '''
    Protected Overrides Sub Edit(ByVal source As CurrencyManager,
        ByVal rowNum As Integer, ByVal bounds As System.Drawing.Rectangle,
        ByVal [readOnly] As Boolean, ByVal displayText As String,
        ByVal cellIsVisible As Boolean)
        Return
    End Sub
    '''
    ''' The grid normally paints the active cell with a different colour to
    ''' the other cells. As our grid is read-only, this isn't a desirable
    ''' effect. This overridden Paint method
    ''' ensures that the cell is always painted to look the same, regardless
    ''' of whether it is active or not. We will also render Null values in
    ''' grey to distinguish them from the string "(null)".
    '''
    Protected Overrides Sub Paint(ByVal g As System.Drawing.Graphics,
        ByVal bounds As System.Drawing.Rectangle,
        ByVal source As System.Windows.Forms.CurrencyManager,
        ByVal rowNum As Integer, ByVal backBrush As System.Drawing.Brush,
        ByVal foreBrush As System.Drawing.Brush,
        ByVal alignToRight As Boolean)

        Try
            'Create a brush for the background
            backBrush = New SolidBrush(SystemColors.Window)

            'Retrieve the value for this cell
            Dim o As Object = Me.GetColumnValueAtRow(source, rowNum)

            'Is this a Null value?
            If o Is DBNull.Value Then
                'Yes, so we'll use grey text
                foreBrush = New SolidBrush(SystemColors.GrayText)
            Else
                'This is a normal value, we'll use the standard foreground
                'text colour
                foreBrush = New SolidBrush(SystemColors.ControlText)
            End If

            'Call into the base class to do the work
            MyBase.Paint(g, bounds, source, rowNum, backBrush, foreBrush,
                alignToRight)

        Finally
            'Clean up
            If backBrush IsNot Nothing Then backBrush.Dispose()
            If foreBrush IsNot Nothing Then foreBrush.Dispose()
        End Try

    End Sub

End Class

As you can see, two of the class procedures are overridden. The Edit procedure exits immediately, without calling into the base class. This prevents the edit from taking place. The Paint procedure does call into the base class (and that's still where the actual drawing of the text takes place), but prior to doing it replaces the foreground and background brushes that would have been used, and substitutes its own to ensure that it has full control over the colours.

If you wish to learn more about changing and extending the behaviour of the DataGrid, you can find a huge amount of information in the DataGrid section of George Shepherd's Windows Forms FAQ.

History

Version 1.2 (2008-09-10)

  • Added support for System.Collections.Generic classes
  • Visual Studio 2008 support added

Version 1.1 (2007-09-14)

  • Added support for collections deriving from Collections.CollectionBase
  • Released source-code

Version 1.0 (2007-06-03)

  • Initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here