Advanced Tree Control

Annotation

This article presents an advanced control based on a standard tree view control. This control supports multiple-selection mode and some visual enhancement compared to the original control. Also, it explains some implementation details and may be useful to anyone who needs to fine-tune this control to his or her needs or even write something completely different.

Introduction

The initial idea of this class was to implement a good-looking multiple-selection tree control for the Yet Another Instant Messenger Project.

Writing a tree view from scratch is not an easy thing to do, so the subclassing seemed to be a nice solution. After a brief search on CodeGuru, I found two classes that seemed to be suitable... if combined together (see the References section at the end of the article).

It was not that interesting just to merge two classes and they didn't cover all my needs, so the final problem statement included the following aspects:

The class must be ATL/WTL-based. The two original ones were MFC-based.

Multiple-selection support. Of course, that's what all this mess about; keyboard navigation and selection should be available too.

Similar to a list control multiple selection interface (GetSelectedCount(), GetFirstSelectedItem(), and GetNextSelectedItem() methods) along with some useful utility methods (SelectChildren(), SelectAll(), and SelectRange() ).

Supports multiple item icons (actually, supports icons, bitmaps, image lists and animations, as a series of bitmaps) that may lead and follow item text.

All kinds of item icons should support transparency (through specifying a transparent color for bitmaps and animations).

Most of the fields are self-explanatory. The only trick there is item image storing. To speed up painting slightly, I've divided the image array into two lists: aPreImages and aPostImages for left and right images, respectively ("left" and "right" here refer to the position relative to the item text). To control a specific image, each image structure contains a special ImageId field that is created as an incremented value of dwNextImageId.

Because actually there are four different image types that should behave similarly, all image structures are inherited from the TreeItemImage interface that is declared this way:

The only unobvious thing here is the bNextFrame parameter of the RenderItem() method. There are two different events on which repainting may occur: First, when a particular area was covered by the other window or just appeared on screen (usual OnPaint() handing); the second case is when you need to display the next frame of animation (you want to get constant-speeded animation, yes?). In the first case, you must play the previous frame of animation; in the latter, the next one, remembering it. bNextFrame controls this item image behavior.

It should be understood that all image types may be mixed freely, even with one tree item.

The initial way to store item-specific data was a usage of the tree item's ItemData value. It was quite easy to implement to store a pointer to TreeItemData structure there, but this approach created several problems:

To remove all items from the tree view without memory leaking, you must recursively delete TreeItemData for each structure.

Other applications may use ItemData value for their internal data. This leads to the third problem:

To determine whether there is a correct data pointed by ItemData field, first I used the IsBadReadPtr() WinAPI function. But, the performance was sluggish and also a series of "first change exceptions" in the debugger output window looked ugly.

So, in the final version, a slightly different approach was used: There is a map m_mapItemData that maps the HTREEITEM handle to the pointer to the TreeItemData structure. While it first seemed to be a less effective solution (you have to perform a lookup every time you need to get or set any item's property), in the long run this wins.

There are two tricks with changing a control's font. The first problem is that we need four fonts actually: normal, bold, italic, and bold italic. The SetFont() function creates all four fonts modifying the LOGFONT structure of the original plain font passed as a parameter. The second problem is an obvious thing; fonts may differ by character height. To calculate the current item's height, this code is used:

As can be seen, you calculate only one font height assuming that all needed variations do not change theirtext height (this is true for all fonts I've seen).

The trick of flickerless redrawing of the control is an undocumented feature of the WM_PAINT message for a tree view control. It seems (for versions of Windows I've tested the control on—98, ME, 2K, and XP) that it is possible to pass custom device context in the WPARAM parameter when calling the original handler for WM_PAINT. This is the same way as WM_PRINT should be called, but I failed to make WM_PRINT work.

Note: The custom implementation of the TransparentBlt() function uses a stock implementation (from msimg32.dll) on all OSes except Win9x because there it leaks resources insanely. Instead, a potentially slower TransparentBltSlow() is used.

Placing the control in a resizable window isn't a wise idea either, because when a tree window's size is changing, the standard tree control handles it by recalculating scroll bar ranges. But, items in your tree may have different horizontal dimensions so there should be another handling that sets the correct ranges. It is implemented in the UpdateHorzScroller() function that is called for each control drawing operation—in the WM_PAINT message handler. However, it is impossible to disable the default handling without impairing other functionality, so some flicker of the horizontal scroll bar occurs during window resizing.

Another problem is with tool-tip text for long items. Their appearance is based on the "original" item positions—for a non-skinned tree control. Tool-tips should also be handled manually.

Why the class name is CCherryTree? It is up to you guys to guess it.

What's Next?

This control still is missing some features that may be useful, but I'm too lazy to implement them right now:

Custom cursors. Currently, the control uses a hand cursor for expandable items an and arrow cursor for all other areas. This behavior depends on the TVS_TRACKGROUPSELECT style. If this style is not set, the control uses an arrow cursor for the whole area.

Transparency. The control behaves perfectly when it has any custom background color, but as all complex Windows controls, I doubt it will be an easy task to make it transparent. All those painting shortcuts used internally (as partial repaint in WM_HSCROLL and WM_VSCROLL that completely bypass WM_PAINT) make it a painful thing to do.

Property inheritance. It may be useful in some applications to allow inheritance of all per-item properties—color, font properties, and indent.

Custom drawing support. "More custom? How's that?" you may ask. Callback texts, user-supplied images, some pre- or post-painting special effects may be useful. This requires some callback hooks (because the original tree view's ones are already used) to be supplied.

Per-item font support. The biggest problem with this feature is that this tree view control supports only fixed-height items. I didn't experiment combining the TVS_NONEVENHEIGHT style with this implementation.

Multi-line items or even RTF-based items. The same problem as with per-item font support exists. Although this way the control will even emulate Outlook's mail list control (group by date, sender, and so forth)

Downloads

Comments

Good work

Bug with drawing

Posted by johny555
on 07/14/2010 07:43am

When the second item in the list has long and enough children to extend the CherryTree both down and along, pressing the up arrow makes it constantly flicker and redraw itself until the down arrow is pressed. This means you can't get to the top item without first hiding the children of the second member.

More info

Posted by hi1
on 07/15/2010 09:25am

This actually happens if the first item has enough children to go off the bottom of the screen too. When you initially double click it, it's fine, if you then move down the list, and then go back to the top, when you reach the top of the list it flickers constantly until you move down by one item. This means if you are trying to hide the children of the first item it is not possible if they go out of the bottom of the control. This problem does not occur when there are not enough children to force a vertical scroll bar to be drawn.
I would love it if someone could fix this, pretty please...

TVS_HASBUTTONS | TVS_HASLINES

Ctrl and Shft Multiselect in Windows 7

Posted by JarmoP
on 04/15/2010 09:41am

I used your class to replace multi-select tree control from Hazlewood, while it did not work in Vista / Windows 7 with control or shift key. Your demo program worked OK, but when I could not get my program to work. It had still the same problem as with Hazlewoods control. What is the trick to get it work?

Problem loading 24x24, 256 color icons

Posted by Chinthak
on 05/27/2007 08:02am

Thanks a lot for this great control. But, (in the MFC version) when I try to load 24x24, 256 color icons using "AddItemIcon" function, it always loads in a bigger size (looks like 32x32). How to overcome this problem?
P.S: I've changed SetItemHeight( 24 ) and return CSize( 24, 24 ) in the function GetSize() but still no luck.

CherryTreeMFC project can't compile in VC++ 6.0

This was a great solution, right up until...

Posted by jhudler
on 06/08/2005 06:12pm

No SetItemData implementation.
You have a Custom method however, because you chose not to sub class the the tree control you can't catch the inserts, deletes and other messages that would have made handling item data and memory management trivial.
Good idea, but I have to say; poor execution.

SetItemData()

Posted by Fahrenheit
on 06/09/2005 03:20am

Thanks for feedback.
Because the control is derived from CTreeControl (in MFC version) and CTreeViewCtrl (for ATL version) you can use their SetItemData / GetItemData methods or even TVM_SETITEM message. Control itself doesn't use item data for any purpose so it is completely safe.
Yes, subclassing control the way you tells could make things slightly easier however all these checks like:
TreeItemData* pData = GetCustomItemData( hItem );
if( pData == NULL )
pData = CreateCustomItemData( hItem );
couldn't be avoided in any case because user may decide to subclass existing tree control (with some items)

Help needed to support for multiple item drag n drop image and item renaming

Posted by hitesh17
on 05/09/2005 09:36am

I tried to modify this code to support for multiple item drag n drop (with multiple item drag image keeping the indentation level) and also the item renaming but it didn't work nicely.
Edit for item renaming appears somewhat shifted not exactly over the item to be renamed.
and dragging the image cause paint issues. [I've enabled the hot tracking of items]
Any comments...
Hitesh