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.

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

Comments and Discussions

maybe it is enought to switch
ptms->hItemEditing = NULL;
before
ShowWindow( ptms->hWndEdit, SW_HIDE);
in EndEditLabelNow()
(ShowWindow causes WM_KILLFOCUS call what with already NULL hItemEditing will be filtered in EndEditLabelNow() start)
but needs to test
t!

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.

I needed support for dynamic changing ICON’s based on an application state. To support this the standard tree control can use the ICON and/or the SELECTED ICON as a I_IMAGECALLBACK and when the item is drawn, the control will ask the parent for the image index to draw.

To support this, the code in the OnPaint() handler must ask the parent for the image if the image is of type I_IMAGECALLBACK. Approximately line 2429 in RGTreeCtrl.cpp you will find the current ICON drawing code:

Code review:
After checking for a valid image list we need to know if it is the SELECTED item or just a normal one. Using pItem->state & TVIS_SELECTED will allow for multiple selection support (which by the way works well).

The next step is to set the nImage value based on the selection and test to see if we need to get the ICON index from the parent.

After testing for a valid parent, the TV_DISPINFO structure is initialized to zero and filled with proper information. The mask is set based on the selection state and sent to the parent via the WM_NOTIFY handler.

If the parent returns NON ZERO from the SendMessage(…) the parent processed the message and set the image index. I didn’t check the mask on return because the parent should only set the TV_DISPINFO.item.iSelectedImage or TV_DISPINFO.item.iImage variable.

On return from the SendMessage( WM_NOTIFY, ... ) the nImage variable is updated with the proper image index to draw.

I needed support for NM_RCLICK notification from the tree to the parent. I
added the following code to WM_RBUTTONUP message. There are a few more
NM_xxx notifications that should be supported, but I only needed the
NM_RCLICK notification.

dear anonymous
its wrong solution
not calling CallWindowProc will not send WM_CONTEXTMENU (why you not using it?)
i think if than CallWindowProc/return 0
can you send me short example of our problem?
t!

pPopUp->TrackPopupMenu(TPM_LEFTALIGN |TPM_RIGHTBUTTON, fromScreen.x, fromScreen.y, AfxGetMainWnd());
*pResult = 0;
}
and when a Menu item(Create) is selected I have a ON_COMMAND function.ON_COMMAND(ID_EDIT_CREATE, OnEditCreate)
When i derive the tree control from CRGTreeCtrl the WM_CONTEXTMENU is executed. That is the reason I have put the code in
WM_RBUTTONUPif ( hItem != NULL )
return 0;
thanks
VJ

till this moment i not fully understood system logic
- it happens in WM_CHAR (but in msdn they speek tree processes WM_KEYDOWN only, btw. in list is no mention about WM_MOUSEMOVE so it must be incomplete or they do any wonder with begin-drag)
- with quicker typing you can search string, after timeout (at mine 1 second) it resets it
- you can get (read only) its search string TVM_GETISEARCHSTRING
- after first letter, say "a", it searches visible item next to selected one starts on mentioned letter, after quick typing of next letter, say "b", it searches for items starting with second, letter ignoring "ab" item (when?), but getting search string you see "ab"
- it seems something in logic depends on wanted item existence (?) or "a..." "ab" "b..." items count and order
- ignores letter case
- after reaching last visible item searches from begin
- TVN_KEYDOWN: If the wVKey member of ptvkd is a character key code, the character will be used as part of an incremental search. Return nonzero to exclude the character from the incremental search, or zero to include the character in the search. For all other keys, the return value is ignored.

-> my prototype looks like this but is not 1:1 from users side and especialy m_ISearchString and systems TVM_GETISEARCHSTRING are different

Due to the limitations of WM_VSCROLL (only 16 bit values are used for the current position!) your scrolling code doesn't work correctly with more than 16k entries. This is the case in the demo application, you can for instance drag the thumbtrack to a low position and it will magically jump up again.

I have checked the code and found out that the right code is almost there, just change the following line in RGTreeCtrl.cpp, line 3039:

One area which bugs me with the Windows controls is that you double up on the actual stored data and have all of the hassles or moving data in and out of the control. For example you might have a list in a CArray which needs to be copied into a CListBox for the user of your app to play with, and then copied back to the CArray when they are done. This can complicate the code quite a bit, especially with tree controls and can waiste valuable time where there is a large amount of data involved. Populate on demand can help with Tree controls, but that isn't always easy to do and again complicates the code. If the tree is displaying dynamic data (stuff comes and goes and changes) everything gets even more complicated.

A better way is to use a model like MFC's Document View framework, where the View (tree/list...) and the Document (data items) it displays are completely separate and independant of each other. This means there is no doubling up of the data, code can be greatly simplified and assuming that data can be accessed quickly, you have very good performance.

I have developed a tree control which follows this model. The View knows how to display a tree, but knows nothing about the data it is displaying nor where it comes from, It simply uses an abstract container class which provides methods like GetRoot, GetFirstChild etc. The application then derives from this abstract class to implement access to the real container of the data. In my case I'm using a Database for several Tree controls and in memory linked style trees for others.

The database can have many hundreds of thousands of items, and is updated in real time. Tree display and performance is quite good, and is limited only by how quickly the database can be accessed. I tried doing similar things with the MS TreeCtrl and gave up as it was way, way too slow.

If you're interesting in seeing this Tree Control in action download a copy of my Programmer's Editor, ED V4 from www.getsoft.com open a nice big VC++ Project and check out the ClassView tree in the Workspace window. If you have the VC++ Source installed there is a sample VC++ Project included with ED4W which you can add to your Project.

Probably it is easier to fill only the first level of the treecontrol and a dummy second-level for all upper levels so that there is a '+' to expand. If the user wants to expand a level you get the TVM_EXPAND Message and you can fill the right information.
I think this is called model/view (doc/view) Pattern. The explorer fills the directory tree in the same way.