What makes a collection bindable to the DataGrid anyway? How do the DataTable, DataView, and DataSet present themselves to a DataGrid or other databound controls? According to the
DataGrid's remarks, anything that implements
IList or
IListSource interfaces.
So what do I have here really? A container (the root) of containers to the infinite level (questions with their own children questions). For a moment IListSource seemed like it since I don't exactly want to bind the actual root container object but the hierarchical nodes within. But how would each node handle its own collection? How am I supposed to return a singular uniform IList for an unknown number in a tree structure?
Upon further inspection and thought, the answer lies in the IList's own implementation of the
ICollection interface, and its
Item property, which gave me a hint to how the DataGrid may be querying a collection to obtain a total count and iteration for each "data item".
The root container is the only class (let's call it HierarchyCollection, and holding a bunch of HierarchyNodes) that ever needs to implement IList. For the practical purposes of this scenario it only has to possess logic in the ICollection.Count and IList.Item indexer properties.
public int Count
{
get
{
int counter = 0;
if (this.topLevelNodes != null) // internal ArrayList of nodes.
{
foreach (HierarchyNode node in this.topLevelNodes)
{
counter += node.CountNodesInTree();
}
}
return counter;
}
} // CountAnd each HierarchyNode contributes to the count recursively.
public int CountNodesInTree()
{
int counter = 1; // counting itself.
if (this.childrenNodes != null)
{
foreach (HierarchyNode child in this.children)
counter += child.CountNodesInTree();
}
return counter;
} // CountNodesInTree()One can of course, use a variable to keep a count of the nodes as they are
inserted into the collection, so that processing is not necessary each time the property is accessed. That is up to you.
So knowing how many items there are in the hierarchy is the first step. But which node has what ordinal number? We will do a similar walk again. Here is where the IList.Item property comes into play.
public object this[int index]
{
get
{
if (this.topLevelNodes == null)
return null; // No collection.
int walkCount = 0;
foreach (HierarchyNode node in this.topLevelNodes)
{
HierarchyNode indexedNode = node.FindNodeByIndex(index, ref walkCount);
if (indexedNode != null)
return indexedQuestion;
}
return null;
} // get
set
{
// My implementation doesn't require modification ;-)
// insert custom set logic here.
}
} // Item indexerSince this is a zero-based index, the beginning of the walk starts with 0 (supposing there is at least one node existing in the collection), and is passed to the first node's tree.
public HierarchyNode FindNodeByIndex(int soughtIndex, ref int walkCount)
{
if (soughtIndex == walkCount)
return this;
walkCount ++;
if (this.children == null)
return null;
foreach (HierarchyNode child in this.children)
{
HierarchyNode indexedNode = child.FindNodeByIndex(soughtIndex, ref walkCount);
if (indexedNode != null)
{
return indexedNode;
}
}
return null;
} // FindNodeByIndex()By passing this counter as a reference, each child node can increment it so as long as it does not match with the index number sought after. When it finally does match, we have found our item and return it back up the tree.
One can also try a tailored binary search algorithm for possible faster searching through a huge hierarchy. That is something I have not had time to test out.
And there you have it, a hierarchical collection of objects that can be presented "flat" to the DataGrid.
I have again run out of time and energy. *yawn* the next part will actually be pretty straight forward - having each Hierarchy object bound to the DataGrid showing the correct columns. Till then.