This seems to me like a reasonably straightforward thing to implement if you design your view model properly.
You basically design the items the same way you would if displaying them in a normal data grid, i.e. each item has a property for each column. In all likelihood, your underlying data model is hierarchical, but the collection that the grid is bound to is going to be flattened, i.e. will contain an item for each node in the hierarchy irrespective of parent/child relationships.
The item view model has some additional properties: Level
, Children
, IsExpanded
, and IsVisible
. Level
is a count of the node's ancestors, Children
contains the child view model nodes, IsExpanded
is used in the UI, and IsVisible
is true if the node is visible. It also implements a property called VisibleDescendants
:
public IEnumerable<NodeViewModel> VisibleDescendants
{
get
{
return Children
.Where(x => x.IsVisible)
.SelectMany(x => (new[] {x}).Concat(x.VisibleDescendants)));
}
}
You use Level
, HasChildren
, and IsExpanded
in the style for the item in the control's first column: they control the left margin and what kind of icon (if any) is displayed.
You also need to implement ExpandCommand
and CollapseCommand
properties. The ExpandCommand
is enabled if Children.Any()
is true and IsExpanded
is false, and the CollapseCommand
is enabled if Children.Any()
is true and IsExpanded
is true. These commands, when executed, change the value of IsExpanded
.
And here's where it gets interesting. The simple way to implement this may work for you: the items are exposed by a parent view model whose Items
property is not a collection. Instead, it's an enumerator that travels down the chain of child view models and yields only the visible nodes:
public IEnumerable<NodeViewModel> Items
{
get
{
return _Items
.Where(x => x.IsVisible)
.SelectMany(x => (new[] {x}).Concat(x.VisibleDescendants));
}
}
Whenever any descendant's IsVisible
property changes, the parent view model raises PropertyChanged
for the Items
property, which forces the data grid to repopulate.
There's a less simple implementation too, where you make the Items
property a class that implements INotifyCollectionChanged
, and that raises the proper CollectionChanged
events when descendant nodes become visible/invisible, but you only want to go there if performance is an issue.