In Part 1, I introduced StackedList<T>, a virtual list of observable sub-lists.
More recently I've been working on a cross between a ListBox and a TreeView (putting hierarchical data inside a list - the exact opposite of ATC's TreeListView). This has some nice benefits: multiple selection, filtering, virtualisation, sorting (although to be honest, sorting an entire TreeView proves an interesting, but perhaps not so useful experience).
To convert a Tree into a List, we have to flatten the data structure. In a tree, a single node is an entity with a value and a list of children. After conversion to a list, a node becomes a list where the head of the list is the value, and the tail is the list of descendent values.
Here's the magic method that kicks this all off:
public static IList<TreeListItem> CreateList(object value) { return new TreeListItem(value).items; }
The constructor, and items are both private, so perhaps this seems bizzare at first. But remember, the head of the resultant list is the TreeListItem that corresponds to the value that was passed in:
this.items = new StackedList<TreeListItem>( new TreeListItem[] { this });
TreeListItem extends ListBoxItem and pushes some bits around to wire up HierarchicalDataTemplates like the real TreeView does. At some later point in time, the item is expanded, and the children ItemsSource is bound.
private void PopulateChildren(Collections.IList newValue) { ... this.flattenedChildren = new FlattenedList<TreeListItem>( new ConvertedList<object, IList<TreeListItem>>( Utils.IList.UntypedToTyped<object>(newValue), delegate(object value) { return new TreeListItem(value).items; })); this.items.AddCollection(flattenedChildren); }
Tada! More virtual lists.
FlattenedList<T> uses a StackedList<T> internally to convert an IList<IList<T>>, a list of lists, into IList<T>. So, FlattenedList is used to flatten a list of child lists into a single list - which then becomes the tail of the list for the current node.
ConvertedList<TInput, T> uses a conversion delegate to convert an IList<TInput> to an IList<T>. ConvertedList is used here to take the list of child nodes of type object, promote these into TreeListItems and then return the list corresponding to each node. So this ConvertedList takes an IList<object> and presents an IList<IList<TreeListItem> - which is then flattened into the parent nodes as described above.
To be as generic as possible, my TreeListItems support binding to child lists of the non-generic type IList or some generic IList<T>, in future I'll probably extend this support to any type that at least implements IEnumerable.
Utils.IList.UnTypedToTyped<T>(IList list) dynamically promotes a non-generic list into a read-only strongly typed IList<T>. This is supported by a couple of additional collection wrapper classes: UpcastList and DowncastList for dealing with IList<T> of some other T, and ObjectList for dealing with a non-generic list.
All the classes mentioned here virtualise the INotifyCollectionChanged events raised by child collections. The end result is that we can flatten an entire hierarchical data structure of observable lists into a single, virtual, observable list.
Stay tuned for part 3, where we will harness the power of virtual lists together with the syntactic goodness of LINQ.
