IntroductionThis 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 codeSo 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 dataUsually 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.