Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Visual Studio Visualizer: Part 1

5.00/5 (8 votes)
10 Jun 2013CPOL5 min read 72K  
This project shows a work around to make a non-serializable object available to a custom visualizer.

Introduction

Project website at: http://vsdatawatchers.codeplex.com

Add a Visual Studio Visualizer to look data from:

  • Dataset;
  • DataTable;
  • DataRow;
  • DataView;
  • DataRowView;
  • DataRowCollection.

Part 2 is available here: http://www.codeproject.com/Articles/584739/Visual-Studio-Visualizer-Part-2-Entity-Framework 

Part 3 is available here: http://www.codeproject.com/Articles/599504/Visual-Studio-Visualizer-Part-3-Collection-visuali

Using the code

Visit the project home page, download your flavor (2010 or 2012), unzip, and place the DLLs in the right folder, Documents\Visual Studio xxxx\Visualizers.

When debugging with VS there is an extra option for the visualiser, for the DataSet, DataTable, DataRow, and DataView objects.

Points of Interest

For some reason arrays are not allowed for visualizing within Visual Studio debug, but when using the VisualizerDevelopmentHost class to test the visualizer, an array is allowed.

This project shows a workaround to make a non-serializable object available to our custom visualizer.

Create a visualizer

This project was created using Visual Studio Express, any edition can be used.

First create a class library project then add a reference to the Visual Studio Visualizer API, the DLL can be found on: Visual Studio installation folder)\Common7\IDE\ReferenceAssemblies\v2.0 Microsoft.VisualStudio.DebuggerVisualizers.dll.

Add references to the types of objects the visualizer will receive:

C#
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DSVisualizer), typeof(VisualizerObjectSource), 
    Target = typeof(DataSet), Description = "My DataSet Visualizer")]
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DTVisualizer), typeof(VisualizerObjectSource), Target = typeof(DataTable), 
    Description = "My DataTable Visualizer")]
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DRVisualizer), typeof(DataRowVisualizerObjectSource), 
    Target = typeof(DataRow), Description = "My DataRow Visualizer")]
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DVVisualizer), typeof(DataRowVisualizerObjectSource), 
    Target = typeof(DataView), Description = "My DataView Visualizer")]
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DRVVisualizer), typeof(DataRowVisualizerObjectSource), 
    Target = typeof(DataRowView), Description = "My DataRowView Visualizer")]
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DRCollectionVisualizer), typeof(DataRowVisualizerObjectSource), 
    Target = typeof(DataRowCollection), Description = "My DataRowCollection Visualizer")] 

This can be inside any cs file of the project, but it must be outside any namespace declaration. As you can see, some types use VisualizerObjectSource and others use DataRowVisualizerObjectSource, this is because the DataRow type needs some massage before we can use it, this is covered later.

The class that will get the object to visualize must inherit from DialogDebuggerVisualizer.

This will allow to override the Show method:

C#
protected override void Show(IDialogVisualizerService windowService, 
          IVisualizerObjectProvider objectProvider) 

The objectProvider will allow to get the instance of the object to visualize:

C#
DataTable dt = objectProvider.GetObject() as DataTable; 

This code snippet is for the datatable visualizer. The rule I followed was one class per type of object.

The windowService is what will show the form or usercontrol to the user:

C#
DialogResult dr = windowService.ShowDialog(mainForm); 

mainForm is a form instance that was initialized with the dt object. Now we can simply bind it to a DataGridView so the user can take a look at the data.

The DataRow

As mentioned before, the datarow type cannot be used as it is, because it doesn't implement the ISerializable interface. So how can we use it?

To get the dataRow, we must create a custom VisualizerObjectSource, so we create the DataRowVisualizerObjectSource that inherits from VisualizerObjectSource, now we can override the GetData method:

C#
public override void GetData(object target, Stream outgoingData)
{
    if (target != null)
    {
        if (target is DataRow)
        {
            DataRow row = target as DataRow;
            DataTable table;

            if (row.Table == null)
            {
                table = new DataTable("DataRowDebuggerTableObjectSource");

                for (int i = 0; i < row.ItemArray.Length; i++)
                {
                    table.Columns.Add(string.Format("Col{o}", i.ToString()), typeof(string));
                }
            }
            else
            {
                table = row.Table.Clone();
            }

            table.LoadDataRow(row.ItemArray, true);

            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(outgoingData, table);
        } 

The trick is to wrap the datarow inside a datatable, because the datatable implements the ISerializable interface.

So now that we have our datarow wrapped we can simple call:

C#
DataTable dt = objectProvider.GetObject() as DataTable; 

Inside our custom debuggerVisualizer, the DRVisualizer, and pass it to the mainForm.

Make the visualizer ready to edit

If the object is editable we can make the visualizer flush the changes made by the user in our visualizer to the object that is been debuged in visual studio.

The objectProvider can tell us if the object is editable in the variable objectProvider.IsObjectReplaceable so we can replace the object by one that have the changes:

C#
DialogResult dr = windowService.ShowDialog(main);
if (objectProvider.IsObjectReplaceable && dr == DialogResult.OK)
    objectProvider.ReplaceObject(ds); 

The ReplaceObject method is one of the methods that are available to persist the changes to the debunging variable, the methods are: ReplaceData(), ReplaceObject(), TransferData() and TransferObject().

The MSDN documentation on these methods:

"Using one of the Replace methods creates a new data object in the debuggee that replaces the object being visualized. If you want to change the contents of the original object without replacing it, use one of the Transfer methods".

The methods TransferObject() and ReplaceObject() sends the object being visualized, but the object must implement ISerializable.

The methods TransferData() and ReplaceData() are used when we need to implement a custom serialization, these methods receive a stream and in ower custom VisualizerObjectSource we can override the ReplaceData() or TransferData().

So in the DataRow case we have, on the visualizer:

C#
if (objectProvider.IsObjectReplaceable && dr == DialogResult.OK)
{
    //DataRow is not serializable, so we serialize the item array,
    //in the TransferData override of the DataRowVisualizerObjectSource we deserialize the
    //itemArray and update the values od the original DataRow
    objectProvider.TransferData(StreamSerializer.ObjectToStream(null, dt.Rows[0].ItemArray)); 
}   

The StreamSerializer.ObjectToStream is a helper function that receives a object and creates a stream, in this case the DataRow itemArray, but it could be a dataTable with the dataRow inside.

C#
public static Stream ObjectToStream(Stream outgoingData, object data)
{
    if (outgoingData == null)
        outgoingData = new MemoryStream();

    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(outgoingData, data);

    return outgoingData; 
} 

So on our custom VisualizerObjectSource we can override the TransferData() method and update the values that where changed:

C#
public override void TransferData(object target, Stream incomingData, Stream outgoingData)
{
    if (target is DataRow)
    {
        DataRow row = target as DataRow;
        var itemArray = StreamSerializer.StreamToObject(incomingData) as object[];

        //the first column was added on the visualizer form, is the status column
        for (int i = 1; i < itemArray.Length; i++)
        {
            if (!(target as DataRow).Table.Columns[i - 1].ReadOnly && 
                            !CompareData.Compare(row, itemArray, i - 1, i))
                 row[i - 1] = itemArray[i];
        }
     }    

The target is the original object so changing it will pass the changes to the variable that the user is debuging.

Test the visualizer

To test the visualizer we have to create a public method in our custom visualizer object, for example to test the visualizer for the datset:

C#
public static void TestShowVisualizer(object objectToVisualize)
{
    VisualizerDevelopmentHost visualizerHost = 
           new VisualizerDevelopmentHost(objectToVisualize, typeof(DSVisualizer)); 
    visualizerHost.ShowVisualizer();
}  

The VisualizerDevelopmentHost object constructor have three overloads, the one above is for a simple visualizer, if we have to use a custom VisualizerObjectSource like the one used in the DataRow visualizer, we have:

C#
public static void TestShowVisualizer(object objectToVisualize)
{
    VisualizerDevelopmentHost visualizerHost = 
      new VisualizerDevelopmentHost(objectToVisualize, 
      typeof(DRVisualizer), typeof(DataRowVisualizerObjectSource));
    visualizerHost.ShowVisualizer();
}

Here the third parameter is of type VisualizerObjectSource. There is another overload where we can define if the objectToVisualize is readonly or not.

One thing I didn't manage to test using the TestShowVisualizer was the ReplaceObject, the dataset that was on debug on VS and passed to the test method was never updated. This only worked using the compiled DLL placed in the VS visualizers folder.

Use the visualizer in VS

To use the visualizer inside Visual Studio (2010 or 2012) see Using the code.

When watching a variable in debug, there is a magnifying glass that allows you to choose several visualizers that registers to that type of variable being watched.

Image 1

If the DLL is in the right folder, there should be two visualizers for dataset, the one shipped width visual studio and this one.

Choosing the the FR DataSet Visualizer will open the visualizer:

Image 2

Here we can see all the data tables, on the tree view and the data, of the selected data table, on the grid view.

Hovering the cells, we can see some more information:

Image 3

On the properties tab, we can see all of the properties of the selected object on the tree view:

Image 4

On the options tab, we can filter the columns that are visible or the data that is displayed, to filter the column we can show only column that contains some strings, separated by ";":

Image 5

Or we can hide the columns that contains some strings, separated by ";":

Image 6

To filter the data, we can set the binding source filter string:

Image 7

There is also a menu with some more options, like accept or reject changes, if the object is editable. We can also see the estimate data size:

Image 8

This project have one option to export the data to excel file, this is done with the EPPlus library. 

History

  • 2013-04-17: Current Visual Studio 2010 and Visual Studio 2012 are supported.
  • 2013-04-24: Testing and using the visualizer.
  • 2013-04-25: Persist changes on an object that don't implement ISerializable
  • 2013-06-10: Links updated. 

License

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