Click here to Skip to main content
Click here to Skip to main content

A faster tree control

By , , 16 Dec 2003
 

Sample Image - RGTree.gif

Introduction

I'm writing an application for exploring the registry. Therefore I need the tree control. First I used InsertItem directly with the registry keys. Inserting an item in the SysTree is fast enough but deleting the items is very slow. I used optimizations described in Tibors article, but it wouldn't be much faster. Then I used text callback items, but the same.

For example: Adding about 50000 registry keys to system tree control needs about 6 sec, deleting these items needs about 22 sec (!) on my Thunderbird 1200.

Because each quick tree control I've found I must pay for and the free controls I've found aren't faster than the standard tree control, I decided to write my own. The tree control should work like the system tree control.

The control internally

The control data storage is based on the template CRGTreeT<_ITEMDATA>. It's an object implementing a bi-directional linked list of elements, each containing a member of type ITEMDATA.

template<class _ITEMDATA> class CRGTreeT
{
 public:
   // Construction / Destruction
   CRGTreeT();
   ~CRGTreeT();

   // returns the count of all items
   UINT GetItemCount() const;
   // returns the count of all child items
   UINT GetChildCount( /*[in]*/ POSITION posParent) const;
   // returns the root
   POSITION GetRootPosition() const;

   // returns the parent of an item
   POSITION GetParentPosition( /*[in]*/ POSITION pos) const;

   // adds an items at the begin of the list
   POSITION AddHead( /*[in]*/ const _ITEMDATA *pItemData, 
                     /*[in]*/ POSITION posParent=NULL);
   // adds an items at the end of the list
   POSITION AddTail( /*[in]*/ const _ITEMDATA *pItemData, 
                     /*[in]*/ POSITION posParent=NULL);

   POSITION InsertAfter( /*[in]*/ const _ITEMDATA *pItemData, 
                         POSITION posParent=NULL, 
                         /*[in]*/POSITION posAfter=NULL);
   // Remove the item at position pos including all subitems
   void RemoveAt( /*[in]*/ POSITION pos);
   // Removes all items
   void RemoveAll();

   // returns the first/last child item of the item at position posParent
   POSITION GetChildPosition( /*[in]*/ POSITION posParent, 
                              /*[in]*/ BOOL bFirst=TRUE) const;
   // returns the next list element, NULL if none
   POSITION GetNextPosition( /*[in]*/ POSITION pos) const;

   // returns the previous element, NULL if none
   POSITION GetPrevPosition( /*[in]*/ POSITION pos) const    

   // returning a copy of the ITEMDATA at position pos
   void GetAt( /*[in]*/ POSITION pos, /*[out]*/ _ITEMDATA* pItemData) const;
   // Returning a pointer to the ITEMDATA at position pos
   _ITEMDATA* GetAt( /*[in]*/ POSITION pos);
   // replace the ITEMDATA at position pos
   void SetAt( /*[in]*/ POSITION pos, /*[in]*/ const _ITEMDATA* pItemData);

   // sort childrens of the item at position posParent
   BOOL SortChildren( /*[in]*/ POSITION posParent, 
                      /*[in]*/ BOOL bAscending=TRUE);
   // search for an item based on the compare function
   POSITION Find( /*[in]*/ const _ITEMDATA *pItemData, 
                  /*[in]*/ POSITION posParent);

   // setting a callback function for sorting and finding items
   CompareFunc SetCompareFunc( /*[in]*/ CompareFunc func);
   DeleteFunc SetDeleteFunc( /*[in]*/ DeleteFunc func, LPARAM lParam);

protected:
   // remove all childs from an item
   void RemoveChilds( POSITION pos);

   // quick sort algorithms for sorting up- or downwards
   void QSortA( /*[in]*/ CTreeItem **pLow, /*[in]*/ CTreeItem **pHigh);
   void QSortD( /*[in]*/ CTreeItem **pLow, /*[in]*/ CTreeItem **pHigh);

private:
   CTreeItem      m_ItemRoot;
   UINT           m_nCount;
   CompareFunc    m_pCompareFunc;
   DeleteFunc     m_pDeleteFunc;
   LPARAM         m_lParamDeleteFunc;
};

The messages will be processed in its own window procedure. Some small functions I've implemented directly, the others exists as stand alone functions with the following form

LRESULT On[MessageName]( HWND hwnd, TREE_MAP_STRUCT *ptms, 
                         WPARAM wParam, LPARAM lParam)

There is a data structure holding the characteristics for each tree HWND:

struct TREE_MAP_STRUCT
{
   HWND         hwnd;                  // the handle of the control
   HWND         hWndEdit;              // the handle of the controls 
                                       //   edit window
   WNDPROC      pOldWndProc;           // the old window procedure
   int          nItemCount;            // all items inserted
   int          nMaxWidth;             // max width of the control
   int          nVScrollPos;           // vertical scroll position
   int          nHScrollPos;           // horizontal scroll position
   HTREEITEM    hItemFirstVisible;     // items ...
   HTREEITEM    hItemSelected; 
   HTREEITEM    hItemEditing;
   HTREEITEM    hItemClicked;
   HIMAGELIST   hNormalImageList;      // image list
   HIMAGELIST   hStateImageList;
   bool         bRedrawFlag;           // should the control be redrawed 
                                       //   on each inserting/erasing
   _rgTree      rgTree;                // the CRGTreeT template
};

For faster calculating of the item widths Tibor has written class holding repeatable used objects, especially the HDC and the HFONTs. You can cache their creation by SetRedraw() calls.

Next optimization is used in the internal GetItemVisibleIndex(). With many items it tries to find the items near the first visible one.

class CItemRectCache
{
public:
   CItemRectCache(HWND hWnd);
   ~CItemRectCache();
   void SetItem( RGTVITEM* pItem);
   int GetItemTextWidth();
   SIZE GetStateImageSize(TREE_MAP_STRUCT *ptms);
   SIZE GetNormalImageSizeAndOffset(TREE_MAP_STRUCT *ptms);
protected:
   bool         m_bHasLinesAtRoot;
   bool         m_bStateImageSizeInitialized;
   SIZE         m_StateImageSize;
   bool         m_bNormalImageSizeInitialized;
   SIZE         m_NormalImageSize;
   HWND         m_hWnd;
   HDC          m_hDC;
   HGDIOBJ      m_hFont;
   HGDIOBJ      m_hBoldFont;
   HGDIOBJ      m_hOldFont;
   bool         m_bBoldSelected;
   RGTVITEM*    m_pItemSet;
}

Known to-dos and limitations

  • Full(er) TreeCtrl functionality. At the moment we have only implemented cases interesting for us. The main missing cases are:
    • Custom drawing
    • TVM_INSERTITEM with TVI_SORT
    • TVM_SETSCROLLTIME
    • Tooltip support
    • Infotips
    • Keyboard notification and incremental search
  • Slower sorting because the quicksort algorithm is used and an array of all items to sort must be built and after sorting the previous/next position of an item must be updated
  • More straight code without duplicities and not commented parts
  • More 1:1 implementation
    • Are cases when hItemFirstVisible after Expand call is not the same like in system tree.
    • Edit control size and position depending on horizontal scrollbar position.
  • More and more intelligent done user interface optimization
    • Can be sometimes more redraws than necessary.
    • More searches can be optimized (see __GetItemVisibleIndex call in SetItem).
    • See bInSetScrollBars usage as horror example.
  • Absolutely correct code
    • Now change of item's TVIS_BOLD state not recomputes horizontal scrollbar.
    • Directly defined ID_EDIT can conflict with your dialog control id (?).
  • With SetRedraw(false) before InsertItem()/TVIF_TEXT system tree returns item's rectangle with 0-widthed text, rgtree with computed size. System tree needs explicit SetItemText() or SetRedraw(true) before GetItemRect(... , true).

The demo project

I've written a demo project comparing the speed of the CRGTreeCtrl with the standard tree control. It measures the time for inserting, sorting and deleting items.

At first you must build RGTree than TreeDlg configuration.

We spent some time searching why the release version is slower than debug in DeleteAllItems(). You will not believe it was in the delete[] pItemData->pszText call. The funny thing is all is ok when you do not run it from developer studio (why did we not find it sooner?).

How to use in your project

If you want to use it build (release) RGTree.dll. This will produce import library RGTree.lib. Include it into Project\Settings\Link\General\Object/Library modules. Derive you tree control from CRGTree

CYourTreeCtrl.h

#include "RGTreeCtrl.h"
class CYourTreeCtrl : public CRGTreeCtrl

change all CTreeCtrl mentions like

BEGIN_MESSAGE_MAP(CYourTreeCtrl, CTreeCtrl)

to CRGTreeCtrl into CYourTreeCtrl.* and rebuild. Do not forget call SetRedraw() before and after big tree data changes.

If you will create own project for tree sources do not forget define RGTREECTRL_EXPORTS there.

Finally

Generally the control is usable. Especially inserting/deleting many items is faster comparing to the system one.

If you find corrections, improvements or add functionality let know to update this article. At first we are interested for bugs (with defined situations) and absolutely the best (you are programmers) with possible solutions.

If you're interested to join us and you don't have problems reading the sources, you're very welcome. Please contact us before your changes to avoid duplicate development.

Update history

  • InvalidateItemRect() calls GetItemRect() with FALSE
  • OnGetItem()/TVIF_TEXT fix
  • HitTest() knows TVHT_TOLEFT and similar flags
  • WM_MOUSEWHEEL handler

25 Jan 2002:

  • InsertItem_UpdateVScrollPos()
  • HitTest() vs horizontal scroll

2 Mar 2002:

  • SetTVITEMforNotify()
  • DoScroll()
  • GetNextItem()/TVGN_LASTVISIBLE

15 Apr 2002:

  • edit and scroll timers using GetDoubleClickTime()
  • TVS_FULLROWSELECT works without TVS_HASLINES only
  • drag and drop support
  • IsBeforeFirstVisibleItem()

23 May 2002:

  • TVE_COLLAPSE fix

7 Aug 2002:

  • NM_RCLICK thanks to Doug Garno
  • I_IMAGECALLBACK thanks to Doug Garno
  • DeleteObject(hBrush) into OnPaint()
  • SetItem() with image change cases

12 Sep 2002:

  • WM_CONTEXTMENU vs NM_RCLICK's return
  • Expand() in SetItem() without notify
  • GetNextItem(NULL) and similars newly will return NULL and not crash
  • MessageBeep() for not handled events to OnKeyDown()
  • two TVGN_DROPHILITE items - second one for "pressed" state (but pressed rgtree still enables by-tab switching)
  • nmtv.itemNew corrected to nmtv.itemOld in TVN_DELETEITEM thanks to Doug Garno
  • (mask | TVIS) corrected to (mask & TVIS) in SetTVITEMforNotify() thanks to Doug Garno

13 Sep 2002:

  • GetChildItem(NULL) returns root item and not NULL thanks to Doug Garno

9 Dec 2002:

  • no WM_CHAR beeps in editing thanks to Doug Garno
  • EndEditLabelNow() sends one TVN_ENDLABELEDIT only thanks to Doug Garno
  • SetTVITEMforNotify() into OnEditLabel() uses lParam instead of ptms->hItemEditing thanks to Doug Garno
  • font handling made more straight

28 Mar 2003:

  • better (but still not 1:1) MessageBeep() for not handled events to OnKeyDown() - see article comment
  • hBoldFont into OnPaint() at request only and derived from WM_GETFONT not hOldFont

17 Dec 2003:

  • fixed scrolling problems when inserting items to zero-sized window (_ScrollAbsolutelyDownWhenRequired)
  • new cases into OnKeyDown() thanks to Doug Garno

Please search article comments for actual code updates too.

License

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

About the Authors

Tibor Blazko
Software Developer (Senior)
Slovakia Slovakia
Member
No Biography provided

René Greiner
Web Developer
Germany Germany
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionLicense type Pinmemberalex viptov23 Feb '12 - 22:09 
Could you please clarify type of license of this control? I see that "there is no explicit license"
I'm going to change Microsoft standard control on faster tree view control.
Does it mean that "no explicit license" is similar to "The Code Project Open License (CPOL)" and "No claim of suitability, guarantee, or any warranty whatsoever is provided. The software is provided "as-is"."
Thank you in advance.
AnswerRe: License type PinmemberTibor Blazko23 Feb '12 - 22:49 
AnswerRe: License type PinmemberRené Greiner25 Feb '12 - 6:56 
GeneralRe: License type Pinmemberalex viptov27 Feb '12 - 0:05 
GeneralRECENT VERSION PinmemberTibor Blazko27 Feb '12 - 0:28 
QuestionIs LPSTR_TEXTCALLBACK supported in InsertItem? Pinmemberalex viptov27 Dec '11 - 2:11 
When I tried insert item using LPSTR_TEXTCALLBACK instead of some text I got exception.
InsertItem(TVIF_PARAM|TVIF_TEXT|TVIF_STATE,LPSTR_TEXTCALLBACK,..) -> exception.
Is LPSTR_TEXTCALLBACK supported in InsertItem?
Thank you
AnswerRe: Is LPSTR_TEXTCALLBACK supported in InsertItem? PinmemberTibor Blazko27 Dec '11 - 2:39 
GeneralAdding check box... Pinmemberkim7810252 Mar '10 - 3:15 
I can not add check box style using this contorl.
Is adding check box possible?
Thank you~~ Laugh | :laugh:
GeneralRe: Adding check box... PinmemberTibor Blazko2 Mar '10 - 3:32 
GeneralRe: Adding check box... Pinmemberkim78102526 Apr '10 - 19:01 
GeneralRe: Adding check box... PinmemberTibor Blazko26 Apr '10 - 19:55 
GeneralRe: Adding check box... Pinmemberkim78102526 Apr '10 - 20:16 
GeneralAbout a problem of memory Pinmemberkim78102523 Feb '10 - 22:34 
When I produce items over one million. Application requires over 250Mb for just creating tree control.. Sleepy | :zzz:
Did you have any solution for reducing memory.
Thank you for good article.
GeneralRe: About a problem of memory PinmemberTibor Blazko23 Feb '10 - 22:43 
GeneralI have a better solution for fast addition Pinmemberrm82212 Jan '07 - 4:33 
Just use TVI_FIRST insted of TVI_LAST - because of ListView uses single-linked list Poke tongue | ;-P
GeneralRighttree Scroll Problem PinmemberZ.L. Dong11 Apr '06 - 22:09 
How about RightTree? It aslo has the vertical scrolling problem with tooo many items, how to do with it?
Thanks!!
GeneralRe: Righttree Scroll Problem PinmemberTibor Blazko11 Apr '06 - 23:49 
GeneralUsing with MFC Statically Linked PinmemberJStanford25 Jan '06 - 8:47 
Does anyone have any suggestions for getting this control to work when you have MFC set to be statically linked in?
 
John Stanford
GeneralRe: Using with MFC Statically Linked PinmemberTibor Blazko11 Apr '06 - 23:45 
QuestionLatest version available ? Pinmemberdefenestration4 Aug '05 - 15:48 
Hi,
 
I'm interested in using your class and was wondering if there was a later version available that addresses any of the issues in the Todo List or any bugs reported. If so, where can I get it from ?
 
Thanks in advance!
AnswerRe: Latest version available ? PinmemberTibor Blazko4 Aug '05 - 19:16 
GeneralUnable to build on VS 2003 Pinmembertotem1624 Mar '05 - 16:28 
Hello, has anyone tried building this on VS 2003?
 
The error messages I get are:
 
Compiling...
RGTreeCtrl.cpp
RGTreeCtrl.cpp(3830) : error C2660: 'CTreeCtrl::CreateEx' : function does not take 11 arguments
RGTreeCtrl.cpp(3840) : error C2660: 'CTreeCtrl::CreateEx' : function does not take 8 arguments
 
thanks!

GeneralRe: Unable to build on VS 2003 PinmemberTibor Blazko28 Mar '05 - 18:25 
GeneralScrollbar problem with large amount of items solved!!! PinmemberAlexey Markov4 Feb '04 - 20:07 
Just replace the part of code at the method DoScroll to the following:
 
switch(nScrollCode)
{
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
//nPos = nPosition;
{
SCROLLINFO info = {0};
info.cbSize = sizeof( SCROLLINFO);
info.fMask = SIF_TRACKPOS;
GetScrollInfo(ptms->hwnd, ScrollBar, &info);
nPos = info.nTrackPos;
}

break;
case SB_LINELEFT:
nPos--;
break;
case SB_LINERIGHT:
nPos++;
break;
case SB_PAGELEFT:
nPos -= vCount - 1;
break;
case SB_PAGERIGHT:
nPos += vCount - 1;
break;
}

GeneralRe: Scrollbar problem with large amount of items solved!!! PinmemberTibor Blazko4 Feb '04 - 21:55 
GeneralRe: Scrollbar problem with large amount of items solved!!! PinmemberAlexey Markov5 Feb '04 - 0:29 
GeneralBasic Question Pinmemberrjewell20 Jun '03 - 19:04 
First of all, your tree control is great. With only a few simple changes I have your tree control in my program and all of my speed issues are gone.
 
My question is very basic. I added your control to my program dynamically but I want to learn how I add the list using the resource editor (like the sample program did). In the resource editor you added a CListCtrl but somehow it was being handled by your tree control code. Could you outline the steps for me to accomplish this.
 
Thanks.
Ryan
GeneralRe: Basic Question PinmemberTibor Blazko22 Jun '03 - 1:25 
Questionmultiple selection support in faster tree ctrl? PinmemberQuokka10 Jun '03 - 10:15 
I am very interested in the faster tree control. However, we need a tree control that supports multiple selection. I was wondering if Tibor's faster tree control supports this, and if not, what would it take to do so? Currently we have been using the SECTreeCtrl from Rogue Wave, but it is very buggy and very slow. Also, it is based on the windows list control common control, which supports the multiple selection, whereas I believe the windows tree control common control does not.
 
Robert Smile | :)
AnswerRe: multiple selection support in faster tree ctrl? PinmemberTibor Blazko10 Jun '03 - 19:03 
General.def build comment PinmemberTibor Blazko9 Apr '03 - 3:58 
now i found that if project contains .def file
and debug output file name differs to release
than produced debug implib contains release dll name inside
GeneralThis may not be needed by everyone PinmemberKenneth Kasajian16 Dec '02 - 6:38 
We had the same problem with the Microsoft tree control. We found that deleting entries was very slow. The "fix" for us, if you could call it that, was to collapse the parent node of the node that was being deleted. It sped it up considerably for the application we were using.
 
This may not be an option for everyone, but it's worth considering.
GeneralRe: This may not be needed by everyone PinmemberTibor Blazko16 Dec '02 - 20:09 
GeneralRe: This may not be needed by everyone PinmemberTim Smith30 Mar '03 - 15:17 
GeneralRe: This may not be needed by everyone PinsussAnonymous4 Jan '05 - 3:17 
GeneralSetBkColor problem PinmemberPanr27 Nov '02 - 17:35 
whether i do call the SetBkColor or not, the back-color keeps in white.
 
you may remove a line from OnPaint function:
ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcUpdate, NULL, 0, NULL);
GeneralRe: SetBkColor problem PinmemberTibor Blazko27 Nov '02 - 19:05 
Generalexcuse me PinmemberPanr4 Dec '02 - 3:26 
GeneralRe: excuse me PinmemberTibor Blazko4 Dec '02 - 20:40 
Generalhello PinsussAnonymous28 Oct '02 - 22:01 
i am having TreeCtrl which have check box can any body tell me how to get check box click event or
what iwan to do is to select the child notes when theparent node is selected???
can any oyd help me????????????

GeneralPatch to support new Flat Scroll bars PinmemberDoug Garno25 Oct '02 - 2:38 
Simple search and replace to support flat scroll bars.
 
In the RGTreeCtrl.cpp file:
1) Replace all GetScrollInfo with FlatSB_GetScrollInfo
2) Replace allGetScrollInfo with FlatSB_SetScrollInfo.
 
If the application calls InitializeFlatSB( hTree ) after the creation of the tree control, then the standard scroll bars are flat. If the InitializeFlatSB() routine is not called, the scroll bars are the standard ones.
 
Would be nice to incorporate this into the base code.
 
Doug Garno
ddgarno@earthlink.net
GeneralRe: Patch to support new Flat Scroll bars PinmemberPanr27 Nov '02 - 18:29 
GeneralUser-defined compare function PinmemberAndreas Muegge17 Sep '02 - 4:06 
Hi,
 
I needed my own compare function which handles German umlauts right. There is already a function to set your own function but only in the template class. I have made the following extensions to CRGTreeCtrl:
 
in RGTreeCtrl.h:
 
// make the prototype known
typedef int (CALLBACK* CompareFunc)( LPARAM lParam1, LPARAM lParam2);
 
class RGTREECTRL_API CRGTreeCtrl : public CTreeCtrl
{
...
public:
...
void SetCompareFunc( CompareFunc newFunc);
};
 

in RGTreeCtrl.cpp:
 
void CRGTreeCtrl::SetCompareFunc( CompareFunc newFunc)
{
TREE_MAP_STRUCT *ptms = (TREE_MAP_STRUCT*) ::GetWindowLong(GetSafeHwnd(), GWL_USERDATA);
ptms->rgTree.SetCompareFunc(newFunc);
}
 
The cast is quite ugly - is there another way to do it?
 

Ciao,
Andreas
GeneralRe: User-defined compare function PinmemberTibor Blazko17 Sep '02 - 20:16 
GeneralRe: User-defined compare function PinmemberAndreas Muegge24 Sep '02 - 3:49 
GeneralRe: User-defined compare function PinmemberTibor Blazko24 Sep '02 - 20:23 
GeneralKeyboard beep when editing PinmemberDoug Garno16 Sep '02 - 0:55 
The default procedure for the edit control will beep when pressing RETURN or ESCAPE. The tree considers these as valid keys and should not beep when processing. To remove the beep return 0 (zero) in the WM_CHAR handler if the key was RETURN or ESCAPE. This tells windows that the message was handled and no further processing is needed. Here is what I've done.
 
At the bottom of the WM_CHAR message code I added
if( wParam == VK_RETURN || wParam == VK_ESCAPE )
return( 0 );

 
Doug Garno
ddgarno@earthlink.net
GeneralTVN_ENDLABELEDIT sent multiple times PinmemberDoug Garno16 Sep '02 - 0:49 
Parent receives the TVN_ENDLABELEDIT message when the user presses ESCAPE or RETURN and then also when the edit control looses focus. Parent should only receive one notification that the user has terminated the editing process. Possibly set a flag in the WM_CHAR handler of the edit procedure and when the WM_KILLFOCUS handler sends the notification, check the flag and send the proper termination state.
 
Doug Garno
ddgarno@earthlink.net
GeneralRe: TVN_ENDLABELEDIT sent multiple times PinmemberTibor Blazko16 Sep '02 - 1:06 
GeneralOnEditLabel() problem PinmemberDoug Garno16 Sep '02 - 0:44 
When setting up the NMTVDISPINFO structure for the parent notify, ptms->hItemEditing is used which is not set until after the parent responds. Should use the lParam of the message which is the item that the user want's to edit.
 
Doug Garno
ddgarno@earthlink.net

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 17 Dec 2003
Article Copyright 2001 by Tibor Blazko, René Greiner
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid