Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

FastTree and FastList

4.91/5 (32 votes)
6 Oct 2014LGPL38 min read 62K   974  
Fast and flexible replacing of standard WinForm’s controls: ListBox, CheckedListBox and TreeView

Introduction

The library presents two controls, FastList and FastTree.

These controls are intended to replace standard WinForm’s controls: ListBox, CheckedListBox and TreeView.

Because usually I work with big data sets and I need perfect performance, these controls implement only virtual mode as fastest way of data receiving. The controls do not store text, color, icon, etc. of items. All these data are passed into controls via events or overridden methods. So you need to store your data outside of the control. The controls store only service data such as: current checkbox state, selection state and collapsed/expanded state.

When I developed the controls, I pursued the following goals:

  1. The control must be able to display big data set. By design, no less than 1mln items – without lags.
  2. When data are changed, control must be able to rebuilding without lags and blinking. All secondary previous states (checkboxes, selection, expanding) must be saved after rebuilding.
  3. The control must implement virtual mode and not take large amount of memory.
  4. The control must be flexible and allow to be easy extending and inheriting.
  5. The control must contain wide set of events with cancelling ability. Each user’s action can be programmatically cancelled (selection, unselection, changing of checkbox state, expanding, collapsing, etc.).
  6. Multiselection, checkboxes, icons.
  7. Wide set of customizing: coloring, custom height of items, custom item drawing, intending, visibility.
  8. Built-in drag&drop supporting.

All these requirements were implemented.

General Design

The FastTree and FastList are inherited from one base class called FastListBase.

The FastListBase inherits standard UserControl and implements main functionality: drawing, scrolling, mouse and keyboard handlers, calculations of coordinates, etc. You can immediately inherit your controls from the FastListBase if you need maximum of flexibility and/or extension.

The FastListBase draws vertical list of the items. It does not know anything about data structure. It simply draws linear list of items, wherein each item has own left intending and own height.

Also FastListBase does not contain any events (except inherited from UserControl, of course). It receives all data via virtual methods, such as string GetItemText(int itemIndex), etc.

Virtual methods instead of events allow getting maximum of performance, because we do not pay for event calling. If subclass will not override the method, FastListBase will use default value, returned by own virtual method.

Also, FastListBase stores some specific data, such as HashSet<int> of selected item indices and HashSet<int> of checked item indices. The FastListBase calculates coordinates of each item and stores it too.

The FastList is tiny wrapper over FastListBase. This class overrides virtual methods of the FastListBase and calls user events there. So FastList has one difference from FastListBase: it contains events.

The FastTree is thicker wrapper over FastListBase because it contains specific logic for tree. It overrides virtual methods and calls events too, but somewhere adds own code. Also, it contains additional methods and events for tree data structure.

Usage of FastList

Simplest way to use FastList – drag it to form, and handle event ItemTextNeeded. In handler, you need to return text of item by item index.

For example:

C#
private void fl_ItemTextNeeded(object sender, StringItemEventArgs e)
{
    e.Result = list[e.ItemIndex];//where list - is your data
}

Also, you need to define count of items. You can assign property ItemCount in design mode or at runtime. When ItemCount is assigning, it calls protected method Build() to recalculate coordinates of drawn items.

Note: If your data was changed but count of items remains the same – simply call fastList.Invalidate(). Because FastList does not store data, it will take data from events and will show actual data after repainting. Only if count of items was changed – set ItemCount property.

More examples of FastList usage – see in Tester application.

Checkboxes

There are two modes of checkboxes. By default, FastList stores checkbox states by oneself, in internal storage CheckedItemIndex.

But if you assigned handler to ItemCheckStateNeeded, the control switches to virtual checkboxes mode. In this mode, you need to return check state of item in handler of ItemCheckStateNeeded event.

Also, you can process state changing in handler of event ItemCheckedStateChanged.

Usage of FastTree

FastTree is more complex to usage because it requires tree data structure.

To start tree building, call public method void Build(object root), where root – is root object of your tree. The FastTree does not allow multiple roots, so all your data must be inside of root object (however root node can be hidden by ShowRootNode property).

Note: Root object is needed only to get tree data from your structures. The FastTree does not require some specific type of root, it can be an instance of any type, or even null. But in future, in event handlers, you must return children or text for given object.

Next, there are two ways: usage of IEnumerable or usage of event NodeChildrenNeeded.

Way 1: Handler of NodeChildrenNeeded

If you assigned handler to event NodeChildrenNeeded, the FastTree will call this event to receive children of given node. In this case, you need to return IEnumerable of child objects from the handler for given parent object.

For example, building of tree of directories:

C#
ft.Build(@"c:\") //build tree using string "c:\" as root object
...

private void ft_NodeChildrenNeeded(object sender, NodeChildrenNeededEventArgs e)
{
    var path = e.Node as string;
    e.Children = Directory.GetDirectories(path);//return subdirectories of parent path
}

private void ft_NodeTextNeeded(object sender, FastTreeNS.StringNodeEventArgs e)
{
    var path = e.Node as string;
    e.Result = Path.GetDirectoryName(path);//return name of directory as text of node
}

Here, we get path of parent directory and return list of subdirectories of it.

Way 2: IEnumerable Interface

Another way to build tree – to use IEnumerable interface. This mode is enabled by default if handler of NodeChildrenNeeded is not assigned.

The idea is that data objects implement IEnumerable of its children. The FastTree will try to cast node to IEnumerable. And if the interface is presented – will get children from it. Otherwise – it will terminal node.

If you use this way, you need only call method void Build(object root) to build tree. Other tree nodes will be built automatically.

Also, you can handle NodeTextNeeded event to draw some specific text for the nodes. But if NodeTextNeeded is not assigned, the FastTree will use ToString() method of node objects.

Note: If text of some nodes was changed, you need only call fastTree.Invalidate() method to refresh the control. But if structure of data (for example count of children) was changed – you must to call method Build(object root) to reflect data changes. All previous selected/checked/expanded states will be automatically restored.

More examples of FastTree usage – see in Tester application.

Checkboxes

There are two modes of checkboxes. By default, FastTree stores checkbox states by oneself, in internal storage.

But if you assigned handler to NodeCheckStateNeeded, the control switches to virtual checkboxes mode. In this mode, you need to return check state of node in handler of NodeCheckStateNeeded event.

Also, you can process state changing in handler of event NodeCheckedStateChanged.

Useful Events, Properties and Methods

Events NodeTextNeeded and ItemTextNeeded - handle this event to assign text to the node/item.

Event NodeChildrenNeeded – can return children of the node.

Event NodeCheckStateNeeded and ItemCheckStateNeeded - can return checkbox state of the node/item.

Events NodeIconNeeded and ItemIconNeeded – return image for icon of the node/item.

Events NodeHeightNeeded and ItemHeightNeeded - handle this event if you needed individual height of items. If handler was not assigned – ItemHeightDefault will be used. Note, that you need to call Build() method if height of nodes was changed.

Events NodeBackColorNeeded, NodeForeColorNeeded, ItemBackColorNeeded, ItemForeColorNeeded – are used to set foreground and background color of the nodes/items.

Wide set of events: CanUnselectNodeNeeded, CanSelectNodeNeede, CanCheckNodeNeeded, etc. – these permitting events can cancel appropriate user actions.

Events NodeCheckedStateChanged, NodeExpandedStateChanged, NodeSelectedStateChanged, ItemCheckedStateChanged, ItemExpandedStateChanged, ItemSelectedStateChanged – inform that node/items state was changed.

Events NodeDrag, DragOverNode, DropOverNode, ItemDrag, ItemOverItem, ItemDropOverItem – these events occur when user starts drag/drag over node/drop node. Note that FastTree and FastList supports virtual data model. So if user drag item into the control, outer event handler must to change its data appropriate to dragging result, and call Build() method to rebuild the control. More examples see in FastListDropItemSample and FastListDragItemSample.

Property AllowDragItems - enables Drag&Drop of items.

Events PaintNode and PaintItem – handle this event if you want to make custom drawing of the node/item.

Property Nodes – list of all visible nodes.

Properties ExpandedNodes, SelectedNodes, CheckedNodes – lists of expanded/selected/checked nodes.

Properties SelectedItemIndex, CheckedItemIndex – hashsets of selected/checked item indicies.

Property MultiSelect – enables multiselection.

Property ItemCount – get/set item count of the FastList.

Property ItemInterval - distance between items (in pixels).

Property NodeHeightDefault, ItemHeightDefault – default height of the node/item (in pixels).

Properties ShowIcons, ShowCheckBoxes – shows icons/checkboxes

Method Build(object root) – rebuilds tree structure. Call it if structure of the tree was changed. Do not call it if only text of nodes was changed (call Invalidate() in this case).

Hotkeys

  • Up, Down, PageUp, PageDown, Home, End – select next/previous/first/last node/item
  • (Up, Down, PageUp, PageDown, Home, End) + Ctrl – scrolls the control
  • Enter, Space – changes checkbox state if ShowCheckBoxes enabled, expands/collapses node otherwise
  • Mouse Click – select node/item
  • Mouse Click + Shift – select item’s range (if Multiselection is enabled)
  • Mouse Click + Ctrl – add selected node/item (if Multiselection is enabled)
  • Mouse Drag&Drop - drag&drop
  • Mouse DblClick - expands/collapses node
  • Mouse wheel – scrolls the control
  • Ctrl + A – selects all nodes/items

Performance

Up to 100 000 000 items for FastList. Up to 10 000 000 subnodes per node for FastTree.

History

  • 18th September, 2014 - First release
  • 20th September, 2014 - Virtual checkboxes mode was added. Samples FastListVirtualCheckboxesSample and FastTreeDragAndDropSample were added.
  • 23rd September, 2014 - Memory usage was improved, stress tests were added.
  • 6th October, 2014 - HotTracking and AllowSelectItems properties were added

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)