Click here to Skip to main content
15,886,035 members
Articles / Desktop Programming / WPF

WPF TreeGrid using a DataGrid

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
3 Nov 2017CPOL10 min read 42.1K   4.6K   13   10
Presenting the concept of a TreeGrid control that utilizes the DataGrid

Image 1

Introduction

This article presents a way to create a TreeGrid, 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.

Disclaimer

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.

Background

With all these years gone by and still not a single built in TreeGrid 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 TreeGrid 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:

  1. 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.
  2. 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 TreeView and ListView are not DataGrids... they end up missing a massive number of customization features that the DataGrid offers.
  3. 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. This 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 DataGrid .

Disadvantages

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.

  1. Loading lots of "root" tree elements up front with a lot of child elements that are initially in the "expanded" state takes awhile.
  2. 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.
  3. 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.

Advantages

So with the above disadvantages, I would wager that this implementation is still going to get you better results than a hacked TreeView or 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.

  1. 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 DataGrid .
  2. UI virtualization due to using the DataGrid .
  3. Lazy loading of tree items is supported.
  4. 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.

Implementation

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 "FlatModel".

Here is how it works. My implementation starts at what's called the TreeGridModel. 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.

When a TreeGridElement is expanded or collapsed, the TreeGridModel is again notified and updates the TreeGridFlatModel accordingly.

The major components of the model are summarized below:

  1. 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 TreeGridFlatModel. 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.
  2. 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.
  3. 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.

Performance

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.

Note

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."

Usage

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.

The 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 Name property.

History

  • 11/2/17: Article inception
  • 11/3/17: Fixed some formatting and grammar mistakes. Fixed download link.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionProblem: TreeGridElement is a DependencyObject and has to be created on GUI thread to be bindable Pin
Wojciech Barański7-Apr-19 22:44
Wojciech Barański7-Apr-19 22:44 
GeneralRe: Problem: TreeGridElement is a DependencyObject and has to be created on GUI thread to be bindable Pin
Member 121621611-Mar-22 16:59
Member 121621611-Mar-22 16:59 
QuestionNice! Pin
Member 140786005-Dec-18 4:31
Member 140786005-Dec-18 4:31 
PraiseWorks perfectly Pin
trampel_ba26-Jan-18 3:32
trampel_ba26-Jan-18 3:32 
Questionpossible usage in early .net framework Pin
Member 1345351515-Nov-17 23:11
Member 1345351515-Nov-17 23:11 
AnswerRe: possible usage in early .net framework Pin
LostTime7625-Nov-17 7:18
LostTime7625-Nov-17 7:18 
GeneralRe: possible usage in early .net framework Pin
Member 1345351528-Nov-17 22:31
Member 1345351528-Nov-17 22:31 
Questiondownload missing? Pin
elevator13-Nov-17 8:26
elevator13-Nov-17 8:26 
AnswerRe: download missing? Pin
LostTime763-Nov-17 9:09
LostTime763-Nov-17 9:09 
GeneralRe: download missing? Pin
elevator17-Nov-17 3:59
elevator17-Nov-17 3:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.