This article presents a way to create a
TreeListView, or whatever you want to call it, using the built in WPF
DataGrid. There are many advantages to using the built in
DataGrid; however, there are some shortcomings as well, namely performance using the method presented herein.
This article is intended to be a proof of concept and not a fully deployable / tested solution. In fact, there are a lot of functions that you will find just stubbed out and not implemented. I deemed them not necessary for the overall presentation of the concept. Additionally, there most likely are still bugs in my implementation.
With all these years gone by and still not a single built in T
reeGrid control for WPF from Microsoft. Seriously, what the heck? There are numerous attempts that can be found on Codeproject and online in general to create a T
reeGrid control that can display hierarchical data in a grid like fashion. After doing the research, all of the controls I have encountered in the wild have many more shortcomings than what I think is presented here.
The major shortcomings of the controls that are found online are listed below:
- The control reinvents the wheel. This means that the control is built from the ground up and as a result, misses major functionality. To be a complete control in my mind, it would have to provide all of the same feature set as a
DataGrid: Selection, multiselection, columns, column resizing, column hiding, the crazy amount of customization through templating, etc.. Sadly, people generally build these controls for their specific use case and don't implement the entire feature set of the
DataGrid due to time.
- The control uses a composite of existing controls. For example, trying to convert a
TreeView or a
ListView into a
TreeGrid using clever control templates. Since the T
ListView are not
DataGrids... they end up missing a massive number of customization features that the
- UI virtualization. All of the above methods do not work very well with UI virtualization. If you add a giant number of items, the view control items actually get created in the background. I have actually tried adding items to a
DataGrid before without virtualization and allowed it to create a
DataGrid row with all of the child controls for every data item. I was able to create 100,000 items before my 32 bit process ran out of memory, and it took 'minutes' to create all of those items. The
DataGrid rows did not have a complicated control hierarchy either.
I looked at many professional third party controls such as the XCeed WPF
DataGrid seems to have a hierarchical feature, I think called master views or something; however, the master views feature requires you to pay for the upgraded version. The only control that I encountered that was from a professional company and free was the syncfusion
TreeGrid controls. I tested these out, and while they worked, I decided to not use them due to a few issues. The first was that, I was seriously irked by the fact that I had to install their "studio" crap (which was a large install) just to get access to the DLL I was looking for. Secondly, once I got to using the controls in code, it just seemed like they were hacked together garbage. Like I said, the controls worked, they just smelled awful in the code. Any other third party controls that I found were not free.
So with the above said, wouldn't it be nice if we could reuse the
DataGrid in a semi efficient way to create a
TreeGrid? It is a very sophisticated control that has built in support for UI virtualization, selection, columns, and templating.
There is another tactic for attempting to create a
TreeGrid using a
DataGrid that I encountered online. The
DataGrid has a concept of grouping where you can nest child
DataGrids in a group to create a tree like effect. I assure you that this is not what this article will present. This method has a host of issues, the main one being it just doesn't scale. What if you have a deep hierarchy of tree nodes? Each tree node would have to have its own
I am going to start with the main disadvantages of the approach in this article, so you know up front what you are getting. Using the
DataGrid in the way I will present is not how the designers of the
DataGrid intended for it to be used. As a result, there are several performance issues due to the internal WPF implementation.
- Loading lots of "root" tree elements up front with a lot of child elements that are initially in the "expanded" state takes awhile.
- After some basic performance testing, about 10,000 items can be loaded on initialization without seeing time delays. Don't hold me to that number.
- About 100,000 items overall can be in the open or expanded state in the tree allowing for nodes to be expanded or collapsed without seeing too much of a time delay. Don't hold me to that number.
So with the above disadvantages, I would wager that this implementation is still going to get you better results than a hacked
ListView that doesn't support UI virtualization. So... if you have a
dataset that is 'sorta small' sorta not, I think this solution will work. My use case fits this description exactly. Without going and yelling at the WPF team to fix some internal implementation issues, this is what we have to work with.
- The solution gives you the power of the
DataGrid control 'minus' the
DataGrid grouping feature. I'm sure you could find a way to use the grouping feature, I don't know.. but displaying hierarchical data in a
TreeGrid kinda negates the need to use grouping feature in the
- UI virtualization due to using the
- Lazy loading of tree items is supported.
AlternationCount works. If you look at the screenshot at the top or run the above demo, you will notice that the alternation count works. Ever tried to set an alternation count on a
TreeView control? Some items in the
TreeView will not get the correct index based on where they are in the tree hierarchy.
Treeview items do not have a concept of overall placement within the parent
TreeView, so the best the
TreeView can do is reset the index here or there.
Without further ado, let's discuss how we can trick the
DataGrid into displaying a tree hierarchy. So let's look at the problem here. A tree structure or model generally has roots at the bottom and each root can have a recursive set of children. The
DataGrid control has no concept of hierarchy and it just wants a list of items to display. So therefore, is it possible to convert a tree hierarchy into a list of items; a FLAT list of items?
The answer to the previous question is yes. This is called flattening the model. In my implementation, the flattened tree hierarchy is known as the "
Here is how it works. My implementation starts at what's called the
TreeGridModel allows root tree elements to be added to it. Each tree element can have a recursive set of children. Whenever roots or children are added to parent tree elements, the
TreeGridModel is notified using internal methods. The
TreeGridModel then constructs what is known as the
TreeGridFlatModel based on the events.
TreeGridElement is expanded or collapsed, the
TreeGridModel is again notified and updates the
The major components of the model are summarized below:
TreeGridElement: Exposes several dependency properties that can be bound to from the
DataGrid . The most important property is the
IsExpanded property. When this is changed, the
TreeGridModel is notified so it can update the
TreeGridElement also manages some internal properties such as its parent in the hierarchy and level in the hierarchy. The
Level property of the element can be used to implement the spacing in the column.
TreeGridModel: The model allows root
TreeGridElements to be added to it. All roots are 'shown' or added to the
TreeGridFlatModel but may not be in an expanded state showing child elements. The model updates the
TreeGridFlatModel when it handles various events coming from child
TreeGridElements or the model itself.
TreeGridFlatModel: This is just a wrapper around an observable collection to implement some internal methods. Notably, this can be bound to from the
DataGrid; however, it cannot be modified by the user, because its an internal collection that the
TreeGridModel uses to produce the flattened tree hierarchy.
So instead of explaining how a flattened model is produced from the hierarchical tree model, it's probably better that you go and explore and step through the code yourself. Keep in mind that the above demo only has add, expand, and collapse functionality implemented for proof of concept. Additionally, I want to note that this is 'my' implementation of how to produce a flattened model from a hierarchy, there could be a billion other more efficient ones.
I want to keep this section brief. In essence, my implementation of producing a flat model from the hierarchical model relies heavily on collection range functions. What is the one thing that is missing from observable collections? - Range functions. When items are expanded or collapsed, a range of child items needs to be added or removed from the
FlatModel, and due to the WPF implementation, this operation cannot be batched. This makes it very non performant. Go yell at the WPF team for assuming that nobody would ever want range functions in an observable collection.
You can inherently add range functions to an observable collection or even implement your own, the issue comes into play when a WPF
ItemsControl binds to a collection that implements
INotifyCollectionChanged. If your event passes more than one item added or removed from the collection at a time, you get a nice exception: "Range operations are not supported."
To use the
TreeGridModel, you basically have to create it and then the public property
FlatModel can be bound to using a
DataGrid's ItemsSource property. Once this is done, you can modify the hierarchy by adding, expanding, or collapsing nodes.
TreeGridElement serves as the base class for concrete
TreeGrid items to be implemented by the user. This means that to create your own items, you derive from
TreeGridElement and start adding fields that can be bound to using a
DataGrid. To implement lazy loading, you can use the
IsExpanding event to load children into a node. There is also a property called "
HasChildren" that can be bound to either show or hide the little toggle button for each node.
In the demo, I create a
DataGrid with the first column being a template column. I then create the layout of how I want to display nodes in the cell. Each cell in the first column has a grid with 3 columns. The first column is used to add whitespace so that the
TreeItem child toggle button is properly positioned based on the item's
Level property. The second column is used to show the
TreeItem child toggle button. Here, I just use a
CheckBox, since it is an easy toggle button; however, you could create an arrow. The third column is used to display the item's name by binding to the
- 11/2/17: Article inception
- 11/3/17: Fixed some formatting and grammar mistakes. Fixed download link.