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:
[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:
protected override void Show(IDialogVisualizerService windowService,
IVisualizerObjectProvider objectProvider)
The objectProvider
will allow to get the instance of the object to visualize:
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:
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:
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:
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:
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:
if (objectProvider.IsObjectReplaceable && dr == DialogResult.OK)
{
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.
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:
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[];
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:
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:
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.
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:
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:
On the properties tab, we can see all of the properties of the selected object on the tree view:
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 ";":
Or we can hide the columns that contains some strings, separated by ";":
To filter the data, we can set the binding source filter string:
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:
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.