Introduction
This add-in is essentially an update to Mohammed Barqawi's excellent DataSet Quick Watch add-in. Please reference the article for the original concept and code.
The add-in's implementation was great but notably didn't support typed datasets. I've added a few things to take the original ideas a little further and hopefully make life even easier to visualise a dataset whilst debugging.
What's New?
Typed DataSet support |
This allows all Typed DataSets (directly inherited from System.Data.DataSet ) to be used with the add-in. This also works with typed DataTable s and DataRow s. |
Support for DataTables and DataRows |
This allows you to select a table or row (in code) and load the dataset (selecting that table or row in the output). |
Row Filter Support |
A free text row filter and 'DataViewRowState ' filter has also been added to help with debugging. This functions on a per table basis. |
Visual Enhancements |
The DataSet Watch form has been amended to use a custom 'EnhancedGrid ' control to provide filtering and a slightly more 'colourful' output. If you're not keen on the look of the grid then you can easily change it by changing the EnhancedGrid UserControl.
I didn't quite get round to adding the support for user preferences! |
Using the code
Debugger Expressions
The original concept still stands, but I've made a few amendments to the way we get and check debugger expressions. I refactored the construction of each expression to work based on the input language:
private string GetEvaluationExpression(string type, object selectedObject,
string propOrMethod, string SourceType)
{
switch (SourceType)
{
case LangCSharp:
{
return "(" + type + ")" + selectedObject + propOrMethod;
}
case LangVB:
{
if (type == "int")
type = "System.Int32";
return "ctype(" + selectedObject + ", "
+ type + ")" + propOrMethod;
}
default :
{
throw new ApplicationException("Invalid Source Type : "
+ "Expected 'cs' or 'vb'");
}
}
}
Where str
(the selected text in the debugger) == "myDataTable"
, the line below will assign a language-specific debugger expression string to getTableNameFromTableExpression
:
getTableNameFromTableExpression =
GetEvaluationExpression(TypeDataTable, str, ".TableName", fileExtension);
- C# -
System.Data.DataTable)myDataTable.TableName
- VB -
ctype(System.Data.DataTable, myDataTable).TableName
This particular expression is used to get the tablename from a selected DataTable
.
Typed DataSets
The support for typed datasets comes in when we evaluate an expression that returns an unexpected type. This will happen because the debugger returns the 'actual' type of the expression you've selected - e.g., MyNamespace.MyTypedDataSet.MyTableRow
.
If you selected something other than a DataSet
, DataTable
or DataRow
(or anything that derives directly from any of these), the ExpressionHasError
method would find the text "error:"
in the expression's value.
In the case we return a typed data object (and we return the type name), we then find the base type and evaluate the expression again... If it's a typed dataset, table or row, then we should find the correct (System.Data...
) type second time around.
if (ExpressionHasError(exprDataSet, "{System.Data.DataSet}", str))
{
if (ExpressionHasError(exprDataTable, "{System.Data.DataTable}", str))
{
if (ExpressionHasError(exprDataRow, "{System.Data.DataRow}", str))
{
MessageBox.Show("This is not a DataSet, DataTable or DataRow!",
"DSWatch",MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
private bool ExpressionHasError(Expression expr,
string type, string originalContext)
{
EnvDTE.Debugger debugger = applicationObject.Debugger;
if (type != null)
if (expr.Value.IndexOf("error:")>-1)
return true;
else
{
if (expr.Value != type)
{
Expression baseExpression =
debugger.GetExpression(originalContext +
".GetType().BaseType.Name" , true, 500);
string val = baseExpression.Value.Replace("\"", String.Empty);
string subType = type.Substring(type.LastIndexOf(".")
+1).Replace("}", String.Empty);
return (val != subType);
}
else
return false;
}
else
return expr.Value.IndexOf("error:")>-1;
}
Selecting specific rows
If we've selected a row in the debugger by a variable: myRow
or with an indexer: myDataSet.Tables[0].Rows[0]
, we know we can find the ordinal position of the row (in the table) via the rowID
property. We can then use this information in the output form to select the appropriate row (and table).
Row Filtering
This works with a combination of standard DataView
filtering - using a free text row filter, and a DataViewRowState
filter (useful to show all rows in a particular state). The following applies a combination of both filters.
private void ApplyFilter(bool showAll)
{
try
{
if (_dataSource == null)
return;
if (showAll || (this.rowStateFilter.SelectedIndex == 0 &&
this.rowFilterExpression.Text == String.Empty))
_view = _dataSource.DefaultView;
else
{
_view = new DataView(_dataSource);
if (this.rowStateFilter.SelectedIndex != 0)
_view.RowStateFilter =
(DataViewRowState)Enum.Parse(typeof(DataViewRowState),
this.rowStateFilter.SelectedItem.ToString(), true);
_view.RowFilter = this.rowFilterExpression.Text;
}
this.grid.DataSource = _view;
if (this.FilterChanged != null)
this.FilterChanged(this.grid,
new FilterChangedEventArgs(_dataSource,
_view.Count, _dataSource.Rows.Count - _view.Count));
this.grid.Refresh();
}
catch (Exception ex)
{
MessageBox.Show(String.Format("There was a problem" +
" applying the row filter\n\n{0}", ex.Message),
"Row Filter", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
}
The EnhancedGrid
UserControl defines a FilterChanged
event, passing context information to allow the parent form to alter the text in its Status Bar.