
Introduction
I recently had a situation where I needed to be able to move a part of a tree control's hierarchy to live under a different tree item. As there is no way provided by the control itself I had to write it myself. I have presented it here as I feel it something which may be of use to others.
How to use it
The function to move an item is shown below. To use it simply paste it somewhere where you wish to use it from, along with the callback function typedef, and call it with the tree control whose items you wish to move, the item to move, and the item under which the item should be moved to. Also, you may optionally specify whether the copy the item instead of moving it, a callback for copying the item data, and the position at which to create the new item relative to the new parent.
Documentation
The function is fairly straightforward. A new item is created as a child of the new parent using the item information from the item being moved. This new item is a replica of the item to be moved. Then the function looks at each of the children of the item to be moved and recursively calls itself to move/copy the item's subtree.
(Note that when MoveTreeItem
calls itself it does not pass in the hItemPos parameter, but always passes in TVI_LAST
. This ensures that the ordering of the new subtree matches that of the existing tree.)
If the function is being used to move rather than just copy, once all children of an item have been moved, the function can delete the item moved as the stack is unravelled back to the first call of the function. As it is usual to have item data which needs deleting we must clear the item data before deleting each item so that any TVN_DELETEITEM
handler does not try to delete the data now used by the copy of the item.
If the function is being used to copy (as opposed to move) items, then the items' data will be duplicated. If the data is a pointer to data which is freed on deleting the item, as is often the case, then taking a direct copy of the pointer can result in the pointer being freed more than once. To avoid this a callback function can be used to take a new copy of the data and use that in the copied item. An example of a callback function is used in the demo app, which stores pointers to CString
objects allocated on the heap. The callback function simply allocates a new copy of the CString
as shown here:
static LPARAM CopyData(const CTreeCtrl& tree, HTREEITEM hItem, LPARAM lParam)
{
if (lParam == 0)
return 0;
CString* ps = (CString*)lParam;
CString* psNew = new CString(*ps);
return (LPARAM)psNew;
}
The callback function's address is passed to MoveTreeItems as follows:
MoveTreeItem(m_tree, m_hItemDrag, hItemDrop == NULL ? TVI_ROOT : hItemDrop,
bCopy, CopyData);
The Function
HTREEITEM MoveTreeItem(CTreeCtrl& tree, HTREEITEM hItem, HTREEITEM hItemTo,
BOOL bCopyOnly = FALSE, PFNMTICOPYDATA pfnCopyData = NULL,
HTREEITEM hItemPos = TVI_LAST)
{
if (hItem == NULL || hItemTo == NULL)
return NULL;
if (hItem == hItemTo || hItemTo == tree.GetParentItem(hItem))
return hItem;
HTREEITEM hItemParent = hItemTo;
while (hItemParent != TVI_ROOT &&
(hItemParent = tree.GetParentItem(hItemParent)) != NULL)
if (hItemParent == hItem)
return NULL;
CString sText = tree.GetItemText(hItem);
TVINSERTSTRUCT tvis;
tvis.item.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_PARAM |
TVIF_SELECTEDIMAGE | TVIF_STATE;
tvis.item.hItem = hItem;
tvis.item.stateMask = (UINT)-1 & ~(TVIS_DROPHILITED | TVIS_EXPANDED |
TVIS_EXPANDEDONCE | TVIS_EXPANDPARTIAL | TVIS_SELECTED);
tree.GetItem(&tvis.item);
tvis.hParent = hItemTo;
tvis.hInsertAfter = hItemPos;
if (bCopyOnly && pfnCopyData != NULL)
tvis.item.lParam = pfnCopyData(tree, hItem, tvis.item.lParam);
HTREEITEM hItemNew = tree.InsertItem(&tvis);
tree.SetItemText(hItemNew, sText);
HTREEITEM hItemChild = tree.GetChildItem(hItem);
while (hItemChild != NULL)
{
HTREEITEM hItemNextChild = tree.GetNextSiblingItem(hItemChild);
MoveTreeItem(tree, hItemChild, hItemNew, bCopyOnly, pfnCopyData);
hItemChild = hItemNextChild;
}
if (! bCopyOnly)
{
tree.SetItemData(hItem, 0);
tree.DeleteItem(hItem);
}
return hItemNew;
}
Callback Function typedef
typedef LPARAM(*PFNMTICOPYDATA)(const CTreeCtrl&, HTREEITEM, LPARAM);
History
Version 4 - 24-Apr-2003
- Added callback to copy item data when copying items, so data doesn't get disposed of more than once
Version 3 - 23 Apr 2003
- Added code to prevent items being moved to their descendants (thanks to Matt Korth for reporting this one)
Version 2 - 06 Feb 2003
- Fixed bug where moving a tree item with more than one child loses all but the first child (thanks to Jack Ploeg for spotting this)
Version 1 - 31 Jan 2003