Once upon a time, there was a project where testers and advanced users had to have a tool to watch complex data. That's why I made a tool inspired by the Visual Studio QuickWatch.
It has some extra features, in addition:
- It displays only fields/properties (i.e. members) a developer wants to be seen by users
- It allows to assign user-friendly names to displayed members
- It allows to save a selected sub-tree to a file
A few months ago, I was asked to display data which consists of over twenty classes; each class has about ten different members. It was just too complex to make a one-purpose piece of code to display it. Especially it was almost impossible to maintain it because the data structure changes.
So, how to automate the process? I use attributes to tag members that users should see:
TreeLeafNodeAttribute – Defines a member as a tree leaf, it cannot contain any nodes or leafs. Usually used for strings, numbers and dates.
TreeInternalNode – Defines a member as an internal tree node, it contains nodes or leafs. Usually used for nested structures and collections.
public class Catalog
public string name;
public Source source;
public List<Cd> cds;
Such an object is passed to the builder which is divided into three layers:
- A base layer – Implements the logic of building the tree
- A middle layer – Renders the tree content to a concrete output device
- A top layer – Makes the builder type-safe and handles special cases
The builder walks through all tagged members of a passed object recursively. To get the structure of tagged members, the builder uses the
TreeStructure class to extract the structure using
System.Reflection. The result is cached in a
static hash table because it does not change during the application run. It's re-used in all later builder runs.
The builder distinguishes between two types of internal nodes: collections and others. If the item tagged with the
TreeInternalNode attribute implements
IEnumerable interface, it's displayed as a collection (see the cds node in the picture above) otherwise it's displayed as another internal node (see the source node). If the item with the
IEnumerable interface implements the
ICollection interface or is an
Array in addition the number of items is displayed as well. The text of every displayed internal node is obtained by calling the
ToString method on the node object (the same way like in the Quick Watch in the Visual Studio).
BuildTitle method formats the appearance of leaf nodes. Strings are displayed in apostrophes, numbers without, decimal numbers are displayed with a precision at two decimal places. Dates are displayed as dates. There is a special
NullDateTime property and if the date has the same value like it, it's displayed as
Names of all members can be translated to a user-friendly format if the
TranslateName method is overriden. For example the member name
TrxNum can be translated as Transaction number.
Data Input and Output
The Catalog content is deserialized from an XML data file because it's just the simplest way to populate a structure with some data. In real applications, the structure is usually populated from a database or another data source.
In this sample project, there is an additional functionality in the middle layer of the builder. It sets the
Tag property of the
TreeNode in every internal node and list item. If the node is selected, the user can serialize its whole content to a file which can be used later for reporting bugs or similar purposes.
In fact, the middle layer can have various implementations. In the sample application, I use the standard
System.Windows.Forms.TreeView. But our company uses our own visual components which are based on
DataSets. The middle layer does not need to have a visual output at all. The output can be saved to a database or to an XML file.
- 26th November, 2007: Initial post