This Visual Studio 2008 C# project offers a solution to use a Windows Forms ListView in combination with a TreeView. Regarding Windows Forms, only the ListView is used. I have created my own TreeListView which runs in virtual mode and provides a good performance, even when used with a large number of lines/nodes.


Next we will have a look at the code.

Looking at the code

So what does the trick? Well, I just use a regular ListView. Because it runs in virtual mode, this property "VirtualMode" of the ListView has to be set to true. Also I use the property "CheckBoxes" set to true, because I use it to provide the state whether a line is expanded or collapsed. The checkbox of course will not be displayed. I also use the "OwnerDraw" mode property set to true and draw an arrow-east when the line is not expanded, and an arrow-southeast when a line is expanded.

When the ListView runs in virtual mode, you must never "fill" the control in order that it will display items. The data is taken directly from the data model, whenever the visible lines of the control come into view. Therefore an event handler named "RetrieveVirtualItem" has to be provided. Now all the control needs to know is, how many lines it has.

When a line is expanded, the number of lines of course increases and the control can adjust its scrollbar. When a line gets collapsed, the number of lines decreases.

The ListView just knows it has lines. But with a TreeListView, tree-like organized data has to be mapped to linear organized lines. To visualize the hierarchy, indentation is used as a normal TreeView does in the same way.

How do we map tree-like organized data to linear organized data? Well, first of all, the control sends events for the visual lines in order to get data for these lines. The event handler "RetrieveVirtualItem" therefore must provide appropriate data for each line. Within this handler, the zero based line position is passed and we have to provide a ListViewItem to the event arguments. I use some kind of "mapper", which is a List of IDataNode.

Because the mapper can be accessed as a zero-based array, every item in the mapper corresponds to a line within the listview. A data model can be loaded from an XML-file, I have provided an example model with the project. All nodes within the XML-file will be a DataNode object. When traversing the XML-file's DOM, the tree-organized model will be created, also the indent level will be set within each DataNode that has children. Have a look at the source files IModel.cs as well as Model.cs in order to see the code in detail.

The mapper afterwards will get initialized this way:

This method adds IDataNode references to the mapper:

Next we will have a look on how we provide data to the control.

Providing data

Usually when you first load the model, all lines will be collapsed. Now if you click on an expandable line, the mouse click event handler will obtain the line number and the mapper which holds references to DataNodes, will allow to obtain the appropriate DataNode. Now the model is organized as a composite pattern which simply means that each DataNode holds a collection to child DataNodes. If a line must get expanded, the child DataNode references will be inserted into the mapper at the given position. After this, the mapper's items grow according to the number of children. After this is done, the control's virtual item count will be set to the mapper's count property. This causes the control to fire new RetrieveVirtualItem events and so, the new expanded lines will get displayed.

The DataNode class looks like this:

When the control is displayed first and whenever the control is scrolled or when the number of lines of the control changes (expand/collapse occurs), the control fires events in order to get the data for the visible lines to visualize. Therefore the event handler for the "RetrieveVirtualItem" gets called. The event's argument object provides the line number of the line that needs data. Before the method returns, a ListViewItem has to be provided and associated to the "Item" property of the event's argument object. We will use the line number to lookup the appropriate IDataNode reference in the mapper. With this reference, which is a part of the data model, we will create the ListViewItem the control expects. Here's the code of the handler:

The method MakelistViewItem looks like this:

Next we will have a look on how to collapse/expand lines.



The basic stuff of course is done when you click on the arrow of a line in order to expand or collapse a line. This is the method:


As you can see, the state whether a line is expanded or collapsed will be set within the Expanded property of the ListViewItem, but also within the DataNode object. The latter has to be done because when one collapses a line on top level and re-expands the line, the lines on a higher indent level still hold their state and appear expanded or collapsed, according to their last state.

The method PrepareNodes looks like this:


Setting the number of the VirtualListSize according to the mapper's count is the last job within the PrepareNodes method and is very important.

When we must expand a line, we have to obtain the children as well as sub-children. The DataNode's references will be inserted into the mapper. Here's the method that does the job:


Collapsing a line is easy, just obtain the total number of expanded children and remove the number of DataNode references in the mapper.

Here's the method to obtain the number of expanded children:


In earlier versions, I have obtained the number of expanded entries by traversing the model itself, but it's really easier to walk along the mapper until you reach the starting indent level again.

Of course there is also code for the owner draw part, but if you look at it you will recognize that it's just drawing the appropriate arrow and setting the bounding rectangle's x-position according to the indent level. So there's no need to explain it in detail, I think.

Points of Interest

The first model I have provided as an XML-file is just very simple and doesn't contain lots of nodes. The second model has an entry that allows to expand 65535 subnodes. The VirtualModeTreeListView is very useful when displaying thousands of nodes, especially when a huge amount of child nodes have to be expanded/collapsed. Then you'll experience a great performance, because the ListView itself must never be "filled" with data. Nevertheless some kind of "mapper" that maps line numbers to data objects must be provided and adjusted whenever a node gets expanded/collapsed. But the collection class I have used for the mapper provides "RemoveAt" and "InsertAt" methods, so the mapper must never be cleared and refilled completely, when expanding/collapsing occurs. Have fun with the control and feel free to provide your own data model!


Using XP, strange drawing problems can occurr which is a well known problem microsoft is aware of regarding the underlying common control. However it should all work fine on Vista as well as Windows7.

This article was originally published on October 4th, 2009

About the Author

Peter Brightman

programmer since the 80ties


Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date