Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / MFC
Article

Full-Featured Tree Control

Rate me:
Please Sign up or sign in to vote.
4.68/5 (24 votes)
25 Jul 20054 min read 166.9K   7.6K   91   30
Implementation of a reusable tree control with many features.

Select multiple items by tracking a rubber band rectangle.

Drag and drop multiple items at once.

Customizable context menu.

Introduction

As I was looking for a more advanced tree control, I came across the article Tree Editor by Yossi Patt. The tree control in this article seemed to have most of the features I had need for. Unfortunately this tree control was nearly unusable outside its demo application. I tried to adapt the tree control to suit my needs, but gave up after one day. It would take too much effort for me to get it to work. In lack of an alternative, I decided to create my own full featured tree control. This class should be as independent as possible to get it work in many projects.

The result, I'm reasonably satisfied with, I put here on CodeProject for all people who are interested. Strictly speaking, this article consists of a small class hierarchy. CEditTreeCtrl implements the base functionality and extents CTreeCtrl by the following capabilities:

  • insert items with a keystroke.
  • delete items with a keystroke.
  • edit items with a keystroke.
  • drag and drop (drop 'above', 'below' or 'as child').
  • drag and drop with right mouse button (drop context menu).
  • sort levels or entire subtrees.
  • context menu for most functions.

The class is highly customizable by inheritance. Nearly all methods are virtual. For instance, it is very simple to generally allow renaming of items, but forbid renaming of specific items. The same applies to insertion of new items, deletion and drag and drop operations.

The class CEditTreeCtrlEx extends the class CEditTreeCtrl by the ability of multiple selections of items as well as drag and drop of these selections. CEditTreeCtrlEx might also be used as an example of how to derive a class from CEditTreeCtrl to extend its capabilities by overriding some of its virtual methods.

Acknowledgment

I used code and ideas found in other articles at CodeProject. Many thanks go to:

Using the code

General

Usage is really simple: add all files from the source archive to your own project(s). After having done this, you may use CEditTreeCtrl and/or CEditTreeCtrlEx as a replacement of CTreeCtrl. For fine tuning, derive your own class and override methods to suit your needs.

In most cases it should be enough to override some of the 'permission'-methods to restrict the functionality to specific items. To prevent moving or copying specific items for instance, you override the method:

virtual bool    CanDragItem(TVITEM & item);

This might be implemented as follows:

// If derived from CEditTreeCtrl:
bool CMyOwnTreeCtrl::CanDragItem(TVITEM & item) {
    return (DWORD(item.lParam) == 666) ? false : true;
}

// If derived from CEditTreeCtrlEx:
// Have to take care about multiple selections.
// The one given as argument is the one item that has the focus.
bool CMyOwnTreeCtrl::CanDragItem(TVITEM &) {
    // iterate selected items
    for(HTREEITEM hItem = GetFirstSelectedItem(); hItem; 
                     hItem = GetNextSelectedItem(hItem))
        if(GetItemData(hItem) == 666)
            // At least one item that we must not drag
            return false;
    return true;
}

Multi Select

Generally you have to bear in mind, that CEditTreeCtrlEx will only take care of selected items that are currently visible or 'might be' visible. This means that selected items laying in collapsed subtrees will be ignored!

Multi select can be switched on or off by the method.

void CEditTreeCtrlEx::EnableMultiSelect(bool bEnable = true);

Whether or not multi select is enabled can be obtained by:

bool CEditTreeCtrlEx::IsMultiSelectEnabled() const;

Note that multi select is ON by default. If it is ON, every call to CTreeCtrl::SelectItem() will add an item to the list of currently selected items! Code of the form:

ClearSelection();
HTREEITEM hItem = GetRootItem();
SelectItem(hItem);
hItem = GetNextSiblingItem(hItem);
SelectItem(hItem);
if(IsMultiSelectEnabled())
    // 2 items should be selected
    ASSERT(GetSelectedCount() == 2);
else
    // only one item should be selected
    ASSERT(GetSelectedCount() == 1);

leads to two selected items if multi select is ON. The latest selected item has the focus. If multi select is switched off, only the user is unable to select more than one item, either by using the mouse or the keyboard. The programmer might use:

void CEditTreeCtrlEx::SelectItems(HTREEITEM hFrom, HTREEITEM hTo);
void CEditTreeCtrlEx::SelectAll();

to select more than one item, even if multi select is off.

You can use:

HTREEITEM CEditTreeCtrlEx::GetFirstSelectedItem() const;
HTREEITEM CEditTreeCtrlEx::GetNextSelectedItem(HTREEITEM hItem) const;
HTREEITEM CEditTreeCtrlEx::GetPrevSelectedItem(HTREEITEM hItem) const;

to iterate the selected items. The method:

HTREEITEM CTreeCtrl::GetSelectedItem() const;

works without a difference. It obtains the item with the focus as long as it is selected, too.

Selections might be reset with the use of these methods:

void CEditTreeCtrlEx::ClearSelection(HTREEITEM Except = 0);
void CEditTreeCtrlEx::DeselectItem(HTREEITEM hItem);

To remove all selections except the one that has currently the focus, you can use:

ClearSelection(GetSelectedItem());

Points of Interest

To keep using the sources as simple as possible, the needed resources (cursors) are not included as resources. Instead, they are described in a separate header file CursorDef.h. If you don't like to use these cursors, feel free to define your own, put them to your project's resource and override the CreateCursorMap() method. Note that this method is only called once per session. All objects will use the same cursors.

There are two ways to customize the context menu. First you can override the method DisplayContextMenu() to provide a completely different menu. The other way is to override ExtendContextMenu(CMenu &) to add more functionality to the default menu. The class CEditTreeCtrlEx uses the second way to extend the menu by the items Select All and Select None.

The definition of the keyboard settings is stored in the member variable m_Keymap. This member appears as a three-dimensional array. The first index describes the key to act on, the second index is an indicator for the control-key, and the third index is an indicator for the shift-key. Assign a method of your derived class to the defined key. The method needs to have the following signature:

bool method(HTREEITEM);

Normally the method gets as a parameter the item on which the method should be used on. A function that erases the entire tree by a keystroke could be implemented as follows:

class CMyOwnTreeCtrl : public CEditTreeCtrl {
    //...
    protected:
        virtual bool DoEraseTree(HTREEITEM);
    //...
};

CMyOwnTreeCtrl::CMyOwnTreeCtrl() {
    // Extend the keymap:
    // Ctrl+DEL shall erase the entire tree
    m_Keymap[VK_DELETE][true][false] = 
            method(&CMyOwnTreeCtrl::DoEraseTree);
}

bool CMyOwnTreeCtrl::DoEraseTree(HTREEITEM) {
    return DeleteAllItems() != FALSE;
}

History

  • July, 18 2005 - first implementation.
  • July, 22 2005
    • Bug fixed in OnBegindrag() and OnBeginrdrag().
    • Bug fixed in HandleKeyDown() (don't handle keys while in label edit mode).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here



Comments and Discussions

 
GeneralRe: warning C4503 Pin
hygreen6-Aug-05 18:42
hygreen6-Aug-05 18:42 
GeneralRe: warning C4503 Pin
FriendOfAsherah7-Jan-06 1:22
FriendOfAsherah7-Jan-06 1:22 
GeneralRe: warning C4503 Pin
safety_ruk28-Jun-06 0:00
safety_ruk28-Jun-06 0:00 
GeneralProgram crashed. Pin
WREY26-Jul-05 23:43
WREY26-Jul-05 23:43 
GeneralRe: Program crashed. Pin
Joerg Koenig27-Jul-05 1:35
Joerg Koenig27-Jul-05 1:35 
GeneralTVN_BEGINDRAG Notification Pin
wsy115617-Nov-05 17:16
wsy115617-Nov-05 17:16 
GeneralRe: TVN_BEGINDRAG Notification Pin
Cristina Cotoranu23-Oct-09 0:11
Cristina Cotoranu23-Oct-09 0:11 
GeneralThanks... Pin
David Eckerson25-Jul-05 3:55
David Eckerson25-Jul-05 3:55 
Thanks for your contribution.

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.