Click here to Skip to main content
15,870,130 members
Articles / Desktop Programming / MFC
Article

Move Tree Items

Rate me:
Please Sign up or sign in to vote.
4.92/5 (29 votes)
23 Apr 2003CPOL3 min read 160.3K   3.4K   66   27
Function to move or copy items and their children in a tree control

Dragging an item   After dropping the item

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:

// copy our lParam, which happens to be a CString pointer
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

////////////////////////////////////////////////////////////////////////////
// Function:	MoveTreeItem
// Version:	4
// Created:	24-Apr-2003
// Author:	Paul S. Vickery
// E-mail:	paul@vickeryhome.freeserve.co.uk
////////////////////////////////////////////////////////////////////////////
// Description:
//    Function to move or copy a tree item and its children to another location
//
// Parameters:
//    CTreeCtrl& tree - the tree control which owns the items
//    HTREEITEM hItem - the item to copy/move
//    HTREEITEM hItemTo - the new parent item for the item moved/copied
//    BOOL bCopyOnly - whether to move (copy+delete) or copy (Default: FALSE)
//    PFNMTICOPYDATA pfnCopyData - optional pointer to a function to copy the 
//                         item's data when making a duplicate item
//    HTREEITEM hItemPos - positioning for new (top) item. This can be one of 
//                         TVI_FIRST, TVI_LAST (Default), TVI_SORT, or TVI_ROOT.
//
// Return:
//    HTREEITEM - returns the new tree item created as a copy of hItem
//
////////////////////////////////////////////////////////////////////////////
// You are free to use or modify this code, with no restrictions, other than
// you continue to acknowledge me as the original author in this source code,
// or any code derived from it.
//
// If you use this code, or use it as a base for your own code, it would be 
// nice to hear from you simply so I know it's not been a waste of time!
//
// Copyright (c) 2003 Paul S. Vickery
//
////////////////////////////////////////////////////////////////////////////
// Version 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 (spotted by Jack Ploeg).
//
// Version 1 - 31-Jan-2003
// =======================
// Initial version
// 
////////////////////////////////////////////////////////////////////////////
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;
  // check we're not trying to move to a descendant
  HTREEITEM hItemParent = hItemTo;
  while (hItemParent != TVI_ROOT && 
        (hItemParent = tree.GetParentItem(hItemParent)) != NULL)
    if (hItemParent == hItem)
      return NULL;

  // copy items to new location, recursively, then delete old hierarchy
  // get text, and other info
  CString sText = tree.GetItemText(hItem);
  TVINSERTSTRUCT tvis;
  tvis.item.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_PARAM | 
          TVIF_SELECTEDIMAGE | TVIF_STATE;
  tvis.item.hItem = hItem;
  // we don't want to copy selection/expanded state etc
  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 we're only copying, then ask for new data
  if (bCopyOnly && pfnCopyData != NULL)
    tvis.item.lParam = pfnCopyData(tree, hItem, tvis.item.lParam);
  HTREEITEM hItemNew = tree.InsertItem(&tvis);
  tree.SetItemText(hItemNew, sText);

  // now move children to under new item
  HTREEITEM hItemChild = tree.GetChildItem(hItem);
  while (hItemChild != NULL)
  {
    HTREEITEM hItemNextChild = tree.GetNextSiblingItem(hItemChild);
    MoveTreeItem(tree, hItemChild, hItemNew, bCopyOnly, pfnCopyData);
    hItemChild = hItemNextChild;
  }

  if (! bCopyOnly)
  {
    // clear item data, so nothing tries to delete stuff based on lParam
    tree.SetItemData(hItem, 0);
    // no (more) children, so we can safely delete top item
    tree.DeleteItem(hItem);
  }

  return hItemNew;
}

Callback Function typedef

// typedef for callback function for copying item data
// Passes in the tree control, the item being copied, and the item's data.
// The function should make and return a new copy of the data if required.
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

  • First version

License

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


Written By
Software Developer (Senior)
United Kingdom United Kingdom
Originally from an electronics background, I moved into software in 1996, partly as a result of being made redundant, and partly because I was very much enjoying the small amount of coding (in-at-the-deep-end-C) that I had been doing!

I swiftly moved from C to C++, and learned MFC, and then went on to real-time C on Unix. After this I moved to the company for which I currently work, which specialises in Configuration Management software, and currently program mainly in C/C++, for Windows. I have been gradually moving their legacy C code over to use C++ (with STL, MFC, ATL, and WTL). I have pulled in other technologies (Java, C#, VB, COM, SOAP) where appropriate, especially when integrating with third-party products.

In addition to that, I have overseen the technical side of the company website (ASP, VBScript, JavaScript, HTML, CSS), and have also worked closely with colleagues working on other products (Web-based, C#, ASP.NET, SQL, etc).

For developing, I mainly use Visual Studio 2010, along with an in-house-designed editor based on Andrei Stcherbatchenko's syntax parsing classes, and various (mostly freeware) tools. For website design, I use Dreaweaver CS3.

When not developing software, I enjoy listening to and playing music, playing electric and acoustic guitars and mandolin.

Comments and Discussions

 
GeneralRe: ERs Pin
Paul Vickery1-Feb-03 11:41
professionalPaul Vickery1-Feb-03 11:41 
GeneralRe: ERs Pin
Brad Bruce13-Nov-03 2:41
Brad Bruce13-Nov-03 2:41 

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.