Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Tree Control Offering Drag and Drop, Folder Auto-expansion, New/edit/delete, and Button Move

4.74/5 (27 votes)
29 Mar 2010CPOL3 min read 65.1K   2.6K  
A basic tree control that ties together all the functionality to make items fully editable and moveable.
ExDragDropCtrl

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

C++
//////////////////////////////////////////////////////////////////////
// Move tree item one place up
void CDragDropView::OnBtnUp()
{
    m_Tree.MoveItemUp();
    m_Tree.SetFocus();
}

//////////////////////////////////////////////////////////////////////
// Move tree item one place down
void CDragDropView::OnBtnDown()
{
    m_Tree.MoveItemDown();
    m_Tree.SetFocus();
}

//////////////////////////////////////////////////////////////////////
// Tree selection has changed, so check status of Up and Down buttons
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...

C++
BEGIN_MESSAGE_MAP(CDlgQuickGraphOrganise, CDialog)
 //{{AFX_MSG_MAP(CDlgQuickGraphOrganise)
    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)
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

..

//============================================================
// Tree Callback Message Handlers
//============================================================
//////////////////////////////////////////////////////////////
// A tree item has been moved successfully,
// so swap references to the old tree item with the new
// one. In other words, a tree item has been deleted
// and recreated somewhere else.
void CDlgQuickGraphOrganise::OnTreeMoveComplete(WPARAM wParam,
                                                LPARAM lParam)
{
    HTREEITEM hOld = (HTREEITEM)wParam;
    HTREEITEM hNew = (HTREEITEM)lParam;
    // Sanity check the move parameters
    ASSERT((hNew != NULL) && (hOld != NULL));
    ...
}

//////////////////////////////////////////////////////////////
// A tree item has been deleted successfully,
// so add it to the Deleted Items list
void CDlgQuickGraphOrganise::OnItemDelete(WPARAM wParam,
                                          LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    HTREEITEM hOld = (HTREEITEM)wParam;
    ...
}

//////////////////////////////////////////////////////////////
// If the user is trying to edit the root item,
// make a beep sound and cancel the edit by setting
// the pResult value to 1.
void CDlgQuickGraphSummary::OnBeginLabelEdit(NMHDR *pNMHDR,
                                             LRESULT *pResult)
{
    LPNMTVDISPINFO pTVDispInfo =
       reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);
    HTREEITEM hItem = pTVDispInfo->item.hItem;
    if (hItem == m_Tree.GetRootItem())
    {
        // We don't allow the user to edit the root folder.
        *pResult = 1;
        MessageBeep((UINT)-1);
    }
}

//////////////////////////////////////////////////////////////
// We've successfully changed an item name - we may want to
// post-process the changed item.
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)