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).
  • Supports changing control font (for all items simultaneously).
  • Supports variable animation speed (for the whole control).
  • Supports changing element gap (horizontal distance, in pixels, between item icon and text/icon).
  • Supports setting all needed colors to paint the control (selection color, selected item color, and control background color).
  • Supports these per-item properties—color, text attributes (bold, italic), and indent—horizontal distance between parent's baseline and current item's baseline.
  • Supports setting custom bitmaps for expanded and collapsed item states (all other item bitmaps are drawn to the right of this state bitmap).
  • As smooth a repainting process as possible.

The controls in the sample application look like this:

Implementation Details

To store item-specific data, there is the TreeItemData structure declared as:

class TreeItemData
{
public:
   TreeItemData();

   ~TreeItemData();

   COLORREF itemColor;

   bool bBold;
   bool bItalic;
   UINT nIndent;

   DWORD dwNextImageId;
   CAtlList< TreeItemImage* > aPreImages;
   CAtlList< TreeItemImage* > aPostImages;
};

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:

class TreeItemImage
{
public:
   TreeItemImage() : TransparentColor( DefaultTransparentColor ) {};
   virtual ~TreeItemImage() {};

   virtual bool IsAnimation() = 0;
   virtual void RenderItem( HDC dc, POINT upper_left,
                            bool bNextFrame = false ) = 0;
   virtual CSize GetSize() = 0;

   DWORD dwItemId;

   COLORREF GetTransparentColor();
   COLORREF SetTransparentColor( COLORREF color );

private:
   COLORREF TransparentColor;
};

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.

Child classes: TreeItemBitmap, TreeItemIcon, TreeItemAnimation, and TreeItemImgList implement specific item image types.

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:

// Calculating new item height
CDC dc;
dc.CreateCompatibleDC();
HFONT hOldFont = dc.SelectFont( m_pDefaultItemFont->m_hFont );

CRect rect( 0, 0, 0, 0 );

dc.DrawText( _T( "ABCDEFGabcdefg" ), -1, rect, DT_TOP | DT_LEFT |
              DT_NOCLIP | DT_CALCRECT );
SetItemHeight( rect.Height() + 2 );

dc.SelectFont( hOldFont );

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)

References

Richard Hazlewood July 25, 1999

http://www.codeguru.com/Cpp/controls/treeview/misc-advanced/article.php/c629/

Zafir Anjum August 6, 1998

http://www.codeguru.com/Cpp/controls/treeview/misc-advanced/article.php/c633/



Downloads

Comments

  • Good work

    Posted by Harsha on 05/18/2012 05:34am

    good one for learning

    Reply
  • 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...

      Reply
    Reply
  • TVS_HASBUTTONS | TVS_HASLINES

    Posted by s_renganathan on 05/10/2010 02:11am

    TVS_HASBUTTONS  | TVS_HASLINES style not working ? how can i set that styles

    Reply
  • 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?

    • Workaround

      Posted by JarmoP on 04/16/2010 06:04am

      The problem seems to be that when using Unicode instead of MBCS, setting TVIS_FOCUSED attribute resets TVIS_SELECTED for current selected item. Thus for shift selection the function DoPreSelection must be corrected by adding 
      if (m_hSelect != hItem)
       SetItemState( m_hSelect, TVIS_SELECTED, TVIS_SELECTED); //JP !!!
      after the first SetItemState call. Similarily for control key in DoAction after if (nDone == 1) add:
      HTREEITEM hSel = GetSelectedItem(); //added
      SetItemState(hItem, TVIS_FOCUSED|nState, TVIS_FOCUSED|TVIS_SELECTED); //existing
      if (hSel != NULL && hSel != hItem) //added
       SetItemState( hSel, TVIS_SELECTED, TVIS_SELECTED); //added

      Reply
    Reply
  • 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.

    Reply
  • TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS

    Posted by ReneGerlach on 03/08/2006 11:03am

    How can i enable the original lines and buttons? And how can i setup the TVS_SHOWSELALWAYS

    Reply
  • An error when add a new item

    Posted by lixin on 10/18/2005 10:03pm

    I found an error when add a new item with image after deleting an item sometimes. It seems that it is caused in CCherryTree::DeleteItem, forget a phrase: m_mapItemData.RemoveKey(hItem); Is it right?

    Reply
  • CherryTreeMFC project can't compile in VC++ 6.0

    Posted by li20031120 on 09/15/2005 01:09am

    Feel cool just like eating cherry. Thank you!

    Reply
  • 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)

      Reply
    Reply
  • 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

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds