Introduction
This code ties together a number of things I really wanted my fully draggable/moveable tree control to be able to do, including adding new folders, renaming any items, and deleting any items. The particular things that this control allows are:
- Full drag and drop functionality, including a drag image and indicator to show where the item will be dropped, i.e., to help differentiate between dropping an item in a folder or after the folder
- Move Up/Down buttons for simple item navigation, including into and out of folders
- New folders, renaming, and deletion, including deletion of entire folders
Background
This code is based initially on Frederic My's freeware tree control at www.fairyengine.com. Thanks Frederic.
Using the Code
The extended tree control (CTreeCtrlDrag
) is contained within the source files TreeCtrlDrag.cpp and .h.
To use the control, simply replace the CTreeCtrl
declaration in your dialog with CTreeCtrlDrag
. As it's derived from CTreeCtrl
, it can be initialised as per normal. The dialog then needs to pass any user input down to the tree control as per the following code, which would be in the dialog class...
void CDragDropView::OnBtnUp()
{
m_Tree.MoveItemUp();
m_Tree.SetFocus();
}
void CDragDropView::OnBtnDown()
{
m_Tree.MoveItemDown();
m_Tree.SetFocus();
}
void CDragDropView::OnTreeSelChanged(NMHDR* pNMHDR, LRESULT* pResult)
{
m_fbtnUp.EnableWindow(m_Tree.ShouldUpBtnBeEnabled());
m_fbtnDown.EnableWindow(m_Tree.ShouldDownBtnBeEnabled());
}
void CDragDropView::OnBtnNewFolder()
{
m_Tree.CreateNewFolder();
}
void CDragDropView::OnBtnRename()
{
m_Tree.RenameSelected();
}
void CDragDropView::OnBtnDelete()
{
m_Tree.DeleteSelected();
}
In the application where I use this for real, I link each tree item to a data structure, so I need to know when items are deleted or moved. When a tree item is moved, it is in fact deleted and recreated in the target location. So I need to know what the old and new tree items are. This is done automatically within the CTreeCtrlDrag
control, and takes the form of user defined messages posted back to the parent window (dialog). They would be handled in the calling dialog like this...
BEGIN_MESSAGE_MAP(CDlgQuickGraphOrganise, CDialog)
ON_MESSAGE(UWM_TREEMOVECOMPLETE, OnTreeMoveComplete)
ON_MESSAGE(UWM_TREEITEMDELETED, OnItemDelete)
ON_MESSAGE(UWM_TREEBEGINEDIT, OnBeginLabelEdit)
ON_MESSAGE(UWM_TREEENDEDIT, OnItemRenamed)
ON_MESSAGE(UWM_TREERBUTTONDOWN, OnRButtonDown)
ON_MESSAGE(UWM_TREELBUTTONDBLCLICK, OnBtnCreategraph)
END_MESSAGE_MAP()
..
void CDlgQuickGraphOrganise::OnTreeMoveComplete(WPARAM wParam,
LPARAM lParam)
{
HTREEITEM hOld = (HTREEITEM)wParam;
HTREEITEM hNew = (HTREEITEM)lParam;
ASSERT((hNew != NULL) && (hOld != NULL));
...
}
void CDlgQuickGraphOrganise::OnItemDelete(WPARAM wParam,
LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
HTREEITEM hOld = (HTREEITEM)wParam;
...
}
void CDlgQuickGraphSummary::OnBeginLabelEdit(NMHDR *pNMHDR,
LRESULT *pResult)
{
LPNMTVDISPINFO pTVDispInfo =
reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);
HTREEITEM hItem = pTVDispInfo->item.hItem;
if (hItem == m_Tree.GetRootItem())
{
*pResult = 1;
MessageBeep((UINT)-1);
}
}
void CDlgQuickGraphOrganise::OnItemRenamed(WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
HTREEITEM hItem = (HTREEITEM)wParam;
...
}
In this instance, the user defined messages (e.g., UWM_TREEBEGINEDIT
) are set in StdAfx.h.
Other points to note:
CTreeCtrlDrag
needs to be able to ascertain whether a particular tree item in a folder could be empty, so it assumes the image used for folders is always the same, i.e., using the FOLDER_IMAGE
define. If you're using an image list and the index of the folder image varies from use to use, you'll have to do something a bit more clever here.
Points of Interest
While I've been coding for 4 or so years, I know I've still heaps to learn. Please, if you see any ways to improve this control or my style in general, all constructive feedback would be hugely appreciated. Note - this is only with reference to TreeCtrlDrag.cpp and .h, which are the files I've specifically worked on.
History
- 21 Jan 2010 - Initial release
- 26 Jan 2010 - Fixed a memory leak in
TidyUpEndOfDrag()
- thanks Tom - 26 Jan 2010 - Added a check for duplicate items when moving/renaming. When setting up the tree, call
m_Tree.SetNoDuplicates(TRUE)
, to turn on checking for duplicate items in the current tree folder. See the additional code wherever a move occurs and after renaming (in OnEndLabelEdit()
). - 17 Feb 2010 - Fixed a bug in return messages for movement of folder using the up and down buttons where the message was only being returned for the parent item, not any sub-items. Added a static to display the messages being received by the parent dialog. Added double click response to calling dialog to process a double click on a non-tree item. Added callback for right mouse click to allow calling dialog to context menu options.
- 29 Mar 2010 - Fixed a bug in the control