Implementing Drag And Drop On Tree Items we will implement drag and drop operation on tree control items. We will implement a tree control displaying all the drives and directories of the drives. We can drag the tree item i.e a folder and drop on the desired destination item. The folder actually gets moved to the destination folder.Create a dialog-based application ‘DDtreeitems’. Insert a new class by selecting ‘Insert | New Class’. Enter the class name as CDragDropTree and select CTreeCtrl as the base class. Click the ‘OK’ button. Add the OnCreate( ) handler to the CDragDropTree class and add the code to it as shown below:
int CDragDropTree::OnCreate ( LPCREATESTRUCT lpCreateStruct )
{
if ( CTreeCtrl::OnCreate ( lpCreateStruct ) == -1 )
return –1 ;
m_imglist.Create ( IDB_BITMAP1, 16, 1, CLR_NONE ) ;
SetImageList ( &m_imglist, TVSIL_NORMAL ) ;
CString drive ;
for ( char ch = 'A' ; ch <= 'Z' ; ch++ )
{
drive = ch ;
drive += ":\\" ;
if ( GetDriveType ( drive ) != DRIVE_NO_ROOT_DIR )
{
HTREEITEM hitem = InsertItem ( drive, 0, 0, TVI_ROOT ) ;
InsertItem ( "", 0, 0, hitem ) ;
}
}
return 0 ;
}
In the OnCreate( ) handler, firstly, we have created an image list by using the bitmap IDB_BITMAP1 to be displayed in the tree control. Add a member variable m_imglist of type CImageList to the CDragDropTree class. Since we have to display two different images for drives and folders our bitmap looks like below:
The image list is then set to the tree control by calling CTreeCtrl::SetImageList( ). Next, we have added all the available drives to the tree control. We have also added an empty item to each drive so that ‘+’ sign is shown for the item. When the user expands a particular drive the folders present on that drive get added to the tree. For this, we have handled TVN_ITEMEXPANDING notification in the CDragDropTree class which is received when the user expands the tree. The OnItemexpanding( ) handler is given below:
void CDragDropTree::OnItemexpanding ( NMHDR* pNMHDR, LRESULT* pResult )
{
NM_TREEVIEW* pNMTreeView = ( NM_TREEVIEW*) pNMHDR ;
HTREEITEM hitem = pNMTreeView -> itemNew.hItem ;
if ( pNMTreeView -> action == TVE_EXPAND )
{
deleteitems ( hitem ) ;
CString string = getpathfromitem ( hitem ) ;
adddirectories ( hitem, string ) ;
SortChildren ( hitem ) ;
}
else
{
deleteitems ( hitem ) ;
InsertItem ( "", 0, 0, hitem ) ;
}
*pResult = 0;
}
Before adding the folders to the tree we have deleted the child items by calling a user defined function deleteitems( ) we have passed the handle to the item being expanded to this function. The deleteitems( ) function is given below:
void CDragDropTree::deleteitems ( HTREEITEM hitem )
{
HTREEITEM childitem = GetNextItem ( hitem, TVGN_CHILD ) ;
while ( childitem )
{
DeleteItem ( childitem ) ;
childitem = GetNextItem ( hitem, TVGN_CHILD ) ;
}
}
After deleting the previous items we have called another user defined function getpathfromitem( ) from OnItemexpanding( ) function to get the valid path of the selected item. To this function we have passed the handle to the selected item. This function is given below:
CString CDragDropTree::getpathfromitem ( HTREEITEM hitem )
{
CString pathstr = GetItemText ( hitem ) ;
HTREEITEM hparent ;
CString string ;
while ( ( hparent = GetParentItem ( hitem ) ) != NULL )
{
string = GetItemText ( hparent ) ;
if ( string.Right ( 1 ) != "\\" )
string += "\\" ;
pathstr = string + pathstr ;
hitem = hparent ;
}
return pathstr ;
}
The getpathfromitem( ) function returns a valid path of the item. After the path is obtained we have added folders by calling a function adddirectories( ).We have passed the handle of the item to which folders are to be added and the path returned by getpathfromitem( ) function.
void CDragDropTree::adddirectories ( HTREEITEM hitem, CString path )
{
CFileFind fd ;
if ( path.Right ( 1 ) != "\\" )
path += "\\" ;
path += "*.*" ;
BOOL found ;
found = fd.FindFile ( path ) ;
if ( !found )
return ;
HTREEITEM childhitem ;
while ( found )
{
found = fd.FindNextFile( ) ;
if ( fd.IsDots( ) )
continue ;
if ( fd.IsDirectory( ) )
{
childhitem = InsertItem ( fd.GetFileName( ), 1, 1, hitem ) ;
InsertItem ( "", 0, 0, childhitem ) ;
}
}
}
The folders are browsed by using member functions of CFileFind class. The folders are added to the specified tree item by calling CTreeCtrl::InsertItem( ) function.
After adding the folders to the tree we have sorted them in ascendind order by calling CTreeCtrl::SortChildren( ) function from the OnItemexpanding( ) handler.
Add a variable m_tree of type CDragDropTree class to the CDDtreeitemsDlg class. Create the tree control by calling CDragDropTree::OnCreate( ) function from CDDtreeitemsDlg::OnInitDialog( ) function as shown below:
BOOL CDDtreeitemsDlg::OnInitDialog( )
{
/Appwizard added code
m_tree.Create ( WS_CHILD | WS_VISIBLE | TVS_HASLINES |
TVS_HASBUTTONS | TVS_LINESATROOT,
CRect ( 50, 20, 350, 300 ), this, 1 ) ;
return TRUE ;
}
Now if you run the program you will see the tree control with drives. If you expand the tree you will see the folders displayed in the tree. You can further expand a folder to view its sub-folders.
Now add variables and handlers for drag and drop operation. Add the following variables to CDragDropTree class.
CImageList* m_pdragimage ;
BOOL m_dragging ;
HTREEITEM m_hitemdrag, m_hitemdrop ;
When the user starts dragging an item TVN_BEGINDRAG notification is generated. Add the handler for this notification. The OnBegindrag( ) handler is given below:
void CDragDropTree::OnBegindrag ( NMHDR* pNMHDR, LRESULT*pResult )
{
NM_TREEVIEW* pNMTreeView = ( NM_TREEVIEW* ) pNMHDR ;
m_hitemdrag = pNMTreeView -> itemNew.hItem ;
m_hitemdrop = NULL ;
SetTimer ( 1, 75, NULL ) ;
m_pdragimage = CreateDragImage ( m_hitemdrag ) ;
if ( !m_pdragimage )
return ;
m_dragging = TRUE ;
m_pdragimage -> BeginDrag ( 0, CPoint ( -15, -15 ) ) ;
POINT pt = pNMTreeView -> ptDrag ;
ClientToScreen ( &pt ) ;
m_pdragimage -> DragEnter ( NULL, pt ) ;
SetCapture( ) ;
*pResult = 0;
}
Here, we have retrieved the item the user is dragging. We have set the timer for the scrolling purpose. We have called CTreeCtrl::CreateDragImage( ) function to create a dragging image for the given item in a tree view control CreateDragImage( ) returns pointer to the image list or NULL if no image is associated with the tree control. We have used a set of static functions provided by CImageList class for dragging an image in the window. The CImageList::BeginDrag( ) member function begins a drag operation. The first parameter passed to BeginDrag( ) is the index of the image to drag. The second parameter is the location of the hot spot within the image. The hot spot specifies the coordinates of the starting drag position which is typically the cursor position. We have specified (-15, -15) as the hot spot so that the dragged image is displayed below the cursor. The CImageList::DragEnter( ) function displays the drag image at a position mentioned by the second parameter. The ptDrag element of NM_TREEVIEW structure contains the mouse coordinates relative to the client area at the time when the event occurred. We have called SetCapture( ) function so as to get the mouse messages even if the user drags the image outside the tree control.
Add OnMouseMove( ) handler to the CDragDropTree class. This handler is given below:
void CDragDropTree::OnMouseMove ( UINT nFlags, CPoint point )
{
HTREEITEM hitem ;
UINT flags ;
if ( m_dragging )
{
POINT pt = point ;
ClientToScreen ( &pt ) ;
CImageList::DragMove ( pt ) ;
if ( ( hitem = HitTest ( point, &flags ) ) != NULL )
{
CImageList::DragShowNolock ( FALSE ) ;
SelectDropTarget ( hitem ) ;
m_hitemdrop = hitem ;
CImageList::DragShowNolock ( TRUE ) ;
}
}
CTreeCtrl::OnMouseMove(nFlags, point);
}
If the user is dragging the item we have moved the image to a new location by calling CImageList::DragMove( ) function. Next we have called CTreeCtrl::HitTest( ) function to see if the specified point lies on an item. It returns handle of the item at the specified point. The DragEnter( ) function locks all other updates to the given window during the drag operation. If we need to do any drawing during a drag operation, such as highlighting the target of a drag-and-drop operation we can temporarily hide the dragged image by using the DragShowNoLock( ) member function and perform our operation. Passing FALSE to the function hides the image whereas passing TRUE to it displays the image. As the user moves the cursor on items we have selected the item on which the cursor is currently placed.
Add OnLButtonUp( ) handler to the CDragDropTree class. This handler is given below:
void CDragDropTree::OnLButtonUp ( UINT nFlags, CPoint point )
{
if ( m_dragging )
{
m_dragging = FALSE ;
CImageList::DragLeave ( this ) ;
CImageList::EndDrag( ) ;
ReleaseCapture( ) ;
delete m_pdragimage ;
SelectDropTarget ( NULL ) ;
if ( m_hitemdrag == m_hitemdrop )
{
KillTimer ( 1 ) ;
return;
}
HTREEITEM hitemparent = m_hitemdrop ;
while ( ( hitemparent = GetParentItem ( hitemparent ) ) != NULL )
{
if ( hitemparent == m_hitemdrag )
{
KillTimer ( 1 ) ;
return ;
}
}
CString source, target ;
source = getpathfromitem ( m_hitemdrag ) ;
target = getpathfromitem ( m_hitemdrop ) ;
source += '\0' ;
target += '\0' ;
SHFILEOPSTRUCT sh = { 0 } ;
sh.hwnd = m_hWnd ;
sh.pFrom = source ;
sh.pTo = target ;
sh.wFunc = FO_MOVE ;
sh.fFlags = FOF_NOCONFIRMMKDIR ;
if ( SHFileOperation ( &sh ) != 0 )
{
KillTimer ( 1 ) ;
return ;
}
moveitems( m_hitemdrag, m_hitemdrop ) ;
DeleteItem ( m_hitemdrag ) ;
SelectItem ( m_hitemdrop ) ;
SortChildren ( m_hitemdrop ) ;
Expand ( m_hitemdrop, TVE_EXPAND ) ;
KillTimer ( 1 ) ;
}
CTreeCtrl::OnLButtonUp(nFlags, point);
}
When the user releases the left mouse button we have called the functions CImageList::DragLeave( ) and CImageList::EndDrag( ) to hide the image and to end the drag operation respectively. Next, we have called SelectDropTarget( ) function by passing it NULL to un-highlight the item. If the drag source and drag target are the same or if the drag source is the parent of the drag target the control returns.
Once the drag source and target are finalised we have retrieved the valid path of both the items by calling getpathfromitem( ) function. Since the pTo and pFrom elements of SHFILEOPSTRUCT structures needs double NULL terminated strings we have appended ‘\0’ to the source and target strings. FO_MOVE flag stored in the wFunc element specifies that we intend to move the folders. The SHFileOperation( ) function moves the folder from pFrom to pTo. The function returns 0 if the folder gets moved successfully. If the function fails a system message box is popped up specifying the error.
To move the items in the tree control we have called a user defined function moveitems( ) We have passed the handle of the source and target items to the moveitems( ) function. The moveitems( ) function is given below:
void CDragDropTree::moveitems ( HTREEITEM hbranch,
HTREEITEM hparent )
{
HTREEITEM hchild, hnewitem ;
CString str = GetItemText ( hbranch ) ;
hnewitem = InsertItem ( str, 1, 1, hparent, TVI_LAST ) ;
hchild = GetChildItem ( hbranch ) ;
while ( hchild != NULL )
{
moveitems ( hchild, hnewitem ) ;
hchild = GetNextSiblingItem ( hchild ) ;
}
}
Here, firstly the text of the dragged item is retrieved by calling CTreeCtrl::GetItemText( ) function. A new item is then inserted as a child of the specified item. The drag source may contain child items so to move the entire branch we have called the moveitems( ) function recursively.
If the user drags on the border of the tree control the window should scroll so that the drag target becomes visible. We have set the timer in OnBegindrag( ) function. After every 75 miliseconds it will get checked in the OnTimer( ) function whether the user is dragging on the border or not. The OnTimer( ) function is given below:
void CDragDropTree::OnTimer(UINT nIDEvent)
{
POINT pt ;
GetCursorPos ( &pt ) ;
RECT rect ;
GetClientRect ( &rect ) ;
ClientToScreen ( &rect ) ;
CImageList::DragMove ( pt ) ;
HTREEITEM hitem = GetFirstVisibleItem( ) ;
if ( pt.y <>
{
CImageList::DragShowNolock ( FALSE ) ;
SendMessage ( WM_VSCROLL, SB_LINEUP ) ;
SelectDropTarget ( hitem ) ;
m_hitemdrop = hitem ;
CImageList::DragShowNolock ( TRUE ) ;
}
else
{
if ( pt.y > rect.bottom - 10 )
{
CImageList::DragShowNolock ( FALSE ) ;
SendMessage ( WM_VSCROLL, SB_LINEDOWN ) ;
int count = GetVisibleCount( ) ;
for ( int i = 0 ; i <>
hitem = GetNextVisibleItem ( hitem ) ;
if ( hitem )
SelectDropTarget ( hitem ) ;
m_hitemdrop = hitem ;
CImageList::DragShowNolock ( TRUE ) ;
}
}
CTreeCtrl::OnTimer(nIDEvent);
}
Here, firstly we have retrieved the cursor position and moved the dragged image to the new cursor position by calling CImageList::OnDrag( ) function. We have obtained the first visible item of the tree control by calling CTreeCtrl::GetFirstVisible( ) function. If the user drags on the upper border we have scrolled up by calling SendMessage( ) function and kept the first visible item selected by calling CTreeCtrl::SelectDropTarget( ) function. If the user drags on the lower border we have scrolled down and kept selected the last visible item.
Run the program. You can expand the trees and select the desired item to be dragged and dropped as shown in the following figure.
Download