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:
bool CMyOwnTreeCtrl::CanDragItem(TVITEM & item) {
return (DWORD(item.lParam) == 666) ? false : true;
}
bool CMyOwnTreeCtrl::CanDragItem(TVITEM &) {
for(HTREEITEM hItem = GetFirstSelectedItem(); hItem;
hItem = GetNextSelectedItem(hItem))
if(GetItemData(hItem) == 666)
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())
ASSERT(GetSelectedCount() == 2);
else
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() {
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).