Drag and drop between views in a splitter window
Overwiew:
This code implements a drag & drop operation within an explorer style project (splitter window with left hand treeView right hand listView).
How it works:
Both views have their own implmentation to handle drag & drop within the view. By NOT setting "SetCapture/ReleaseCapture", the drag item can be moved outside the originator view to other targets. If the drag item (mouse cursor) leaves the originators window the ON_WM_MOUSEMOVE()-handler of the target view gets control. Because of that we have to ensure the views can access the same member variable - we use the app-class.
YOUR APP CLASS:
Define some member variables at your app class:
public:
//Drag & Drop Member:
CImageList *cpDragImage;
BOOL cDragging;
CWnd *cpDragWnd;
CWnd *cpDropWnd;
HTREEITEM cTreeItemDrag;
HTREEITEM cTreeItemDrop;
int cListItemDragIndex;
int cListItemDropIndex;
CPoint cDropPoint; //list view needs global var to examine target
YOUR MAINFRAME CLASS:
To access the TreeCtrl from the ListCtrl we can use a small helper function (if you define a "explorer style"-project the method "GetRightPane" is already defined).
CMyListView* CMainFrame::GetRightPane()
{
CWnd* pWnd = m_wndSplitter.GetPane(0, 1);
CMyListView* pView = DYNAMIC_DOWNCAST(CMyListView, pWnd);
return pView;
}
CMyTreeView* CMainFrame::GetLeftPane()
{
CWnd* pWnd = m_wndSplitter.GetPane(0, 0);
CMyTreeView *pTree = DYNAMIC_DOWNCAST(CMyTreeView, pWnd);
return pTree;
}
YOUR TREE VIEW:
To access the member variables, it is useful to define a helper function to easily access the app class:
CMyApp *CMyTreeView::GetApp()
{
return ( (CMyApp*)AfxGetApp() );
}//GetApp
Implement handler for the three messages
ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBegindrag)
Set all elements (this view can set) on begin of drag, perhaps target is the same view, perhaps not.
void CMyTreeView::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW *pTreeView = (NM_TREEVIEW*)pNMHDR;
*pResult = 0;
//Get pointer to associated tree control:
CTreeCtrl &cTree = GetTreeCtrl();
GetApp()->cTreeItemDrag = pTreeView->itemNew.hItem;
GetApp()->cTreeItemDrop = NULL;
//Create a drag image (from the source item):
GetApp()->cpDragImage = cTree.CreateDragImage(GetApp()->cTreeItemDrag);
GetApp()->cpDragImage->BeginDrag(0, CPoint(-15,-15));
POINT pt = pTreeView->ptDrag;
ClientToScreen( &pt );
GetApp()->cpDragImage->DragEnter(NULL, pt);
//Initialize:
GetApp()->cDragging = TRUE;
GetApp()->cpDragWnd = &cTree
GetApp()->cpDropWnd = NULL;
}//OnBegindrag
void CMyTreeView::OnMouseMove(UINT nFlags, CPoint point)
/*----------------------------------------------------------------------------s
Beschreibung:
Parameter:
Rueckgabe:
Autor/Copyright:
(c) Christopher Frank, FRG
----------------------------------------------------------------------------e*/
{
HTREEITEM hitem;
UINT flags;
//Get pointer to associated tree control:
CTreeCtrl &cTree = GetTreeCtrl();
//If dragging is active:
if ( GetApp()->cDragging )
{
POINT pt = point;
ClientToScreen( &pt );
//Move the image:
GetApp()->cpDragImage->DragMove(pt);
//Set selection on the current (possible) target:
if ( (hitem = cTree.HitTest(point, &flags)) != NULL )
{
GetApp()->cpDragImage->DragShowNolock(FALSE);
cTree.SelectDropTarget(hitem);
GetApp()->cTreeItemDrop = hitem;
GetApp()->cpDragImage->DragShowNolock(TRUE);
}
}
//Standardmethode:
CTreeView::OnMouseMove(nFlags, point);
}//OnMouseMove
void CMyTreeView::OnLButtonUp(UINT nFlags, CPoint point)
/*----------------------------------------------------------------------------s
Beschreibung:
Parameter:
Rueckgabe:
Autor/Copyright:
(c) Christopher Frank, FRG
----------------------------------------------------------------------------e*/
{
//Get pointer to associated tree control:
CTreeCtrl &cTree = GetTreeCtrl();
//Standardmethode:
CTreeView::OnLButtonUp(nFlags, point);
//If dragging is active:
if ( GetApp()->cDragging )
{
//Dragging is no longer active:
GetApp()->cDragging = FALSE;
GetApp()->cpDragImage->DragLeave(this);
GetApp()->cpDragImage->EndDrag();
delete GetApp()->cpDragImage;
//Remove drop target highlighting
cTree.SelectDropTarget(NULL);
//Examine the window dragimage is dropped:
GetApp()->cDropPoint = point;
ClientToScreen(&GetApp()->cDropPoint);
GetApp()->cpDropWnd = WindowFromPoint(GetApp()->cDropPoint);
//Select the type of drag source:
if ( GetApp()->cpDragWnd->IsKindOf(RUNTIME_CLASS(CListView)) )
{
AfxMessageBox("source is list view", MB_OK);
}
else
if ( GetApp()->cpDragWnd->IsKindOf(RUNTIME_CLASS(CTreeView)) )
{
AfxMessageBox("source is treeview", MB_OK);
}
else
AfxMessageBox("source is something else", MB_OK);
}
}//OnLButtonUp
YOUR LIST VIEW:
To access the member variables, it is useful to define a helper function to easily access the app class:
CMyApp *CMyListView::GetApp()
{
return ( (CMyApp*)AfxGetApp() );
}//GetApp
CMainFrame *CKAIView::GetFrame()
{
return ( ((CMainFrame*)GetParentFrame()) );
}
CMyTreeView *CMyListView::GetTree()
{
return ( GetFrame()->GetLeftPane() );
}
Implement handler for the three messages
ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBegindrag)
Set all elements (this view can set) on begin of drag, perhaps target is the same view, perhaps not.
void CMyListView::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
*pResult = 0;
//Zeiger auf den Tree-Control ermitteln:
CListCtrl &cList = GetListCtrl();
//set source of drag:
GetApp()->cListItemDragIndex = ((NM_LISTVIEW *)pNMHDR)->iItem;
//Create a drag image (from the source item):
POINT pt;
pt.x = pt.y = 8;
GetApp()->cpDragImage = cList.CreateDragImage(GetApp()->cListItemDragIndex, &pt);
GetApp()->cpDragImage->BeginDrag(0, CPoint (8, 8));
pt = ((NM_LISTVIEW *)pNMHDR)->ptAction;
ClientToScreen( &pt );
GetApp()->cpDragImage->DragEnter(NULL, pt);
//Initialize:
GetApp()->cDragging = TRUE;
GetApp()->cListItemDropIndex = -1;
GetApp()->cpDragWnd = &cList
GetApp()->cpDropWnd = NULL;
}//OnBegindrag
void CMyListView::OnMouseMove(UINT nFlags, CPoint point)
{
//If dragging is active:
if( GetApp()->cDragging )
{
POINT pt = point;
ClientToScreen(&pt);
//Move the image:
GetApp()->cpDragImage->DragMove(pt);
//Get drop window:
GetApp()->cpDragImage->DragShowNolock(FALSE);
GetApp()->cpDropWnd = WindowFromPoint(pt);
GetApp()->cpDropWnd->ScreenToClient(&pt);
GetApp()->cpDragImage->DragShowNolock(TRUE);
//Get tree:
CTreeCtrl &cTree = GetTree()->GetTreeCtrl();
//Remove drop target highlighting on the tree (immediate if the cursor
//leaves the treeView window)
cTree.SelectDropTarget(NULL);
}
//Standardmethode:
CListView::OnMouseMove(nFlags, point);
}//OnMouseMove
void CMyListView::OnLButtonUp(UINT nFlags, CPoint point)
{
//If dragging is active:
if( GetApp()->cDragging )
{
//Initialize end dragging:
GetApp()->cDragging = FALSE;
GetApp()->cpDragImage->DragLeave(GetDesktopWindow());
GetApp()->cpDragImage->EndDrag();
//GET THE WINDOW UNDER THE DROP POINT
GetApp()->cDropPoint = point;
ClientToScreen(&GetApp()->cDropPoint);
GetApp()->cpDropWnd = WindowFromPoint(GetApp()->cDropPoint);
//Cancel if source and target are same:
//Select the type of drag source:
if ( GetApp()->cpDragWnd->IsKindOf(RUNTIME_CLASS(CListView)) )
{
AfxMessageBox("source is list view", MB_OK);
}
else
if ( GetApp()->cpDragWnd->IsKindOf(RUNTIME_CLASS(CTreeView)) )
{
AfxMessageBox("source is treeview", MB_OK);
}
else
AfxMessageBox("source is something else", MB_OK);
}
//Standardmethode:
CListView::OnLButtonUp(nFlags, point);
}//OnLButtonUp

Comments
Problems if dragging outside tree or list ctrl
Posted by Legacy on 01/21/2003 12:00amOriginally posted by: janthony
The drag image sticks on the window border, if you drag outside of the two controls. And even worse, if you release the mouse button when outside of the two controls then the drag image does not go away, and the apps drag state is out of sync.
Better to have the frame window capture the mouse, and have it deligate to the appropriate drop target.
Reply
What is *CKAIView ???
Posted by Legacy on 11/14/2002 12:00amOriginally posted by: Eviral
Hello,
I'm deseperate because i do not see what *CKAIView is ???
What kind of view is it ?
I'm ok for CMyListView, CMyTreeView, CMainFrame, CMyApp, but i really don't know what *CKAIView is !
Thanks
Eviral
ReplyExcellent
Posted by Legacy on 07/02/2002 12:00amOriginally posted by: Benjamins
Great work..
the explanation is simple & clear...thanks
ReplyTree View without an image list
Posted by Legacy on 11/27/2001 12:00amOriginally posted by: Coleman Brumley
If you don't have an image list attached to the tree control, this method asserts. I simply put a condional after the createdragimage call. If the call fails, reset the app members and return from begindrag.
There should be another way around that though.
Replynot working using TVN_BEGINDRAG
Posted by Legacy on 09/12/2001 12:00amOriginally posted by: Hui
My program will failed in BeginDrag if using TVN_BEGINDRAG in Treeview, can anyone help?
Thanks!
ReplyTypo T=L
Posted by Legacy on 08/11/1999 12:00amOriginally posted by: CJ McQuaid
With respect to Victor Vat's query:
CTreeCtrl will generate a TVN_ message, not an LVN_ message as given. Change one letter and all is well.
More power to your elbow!
ReplyProblem: message LVN_BEGINDRAG in TreeView not generated
Posted by Legacy on 05/28/1999 12:00amOriginally posted by: Victor Vat
Very good article!
But I have one problem.
When I drag item from right pane (ListView)
to left pane (TreeView) - its OK.
But, when I drag item from left pane to right pane
message LVN_BEGINDRAG not generated.
What I must do ?
For setting styles I use function PreCreateWindow
BOOL CMyTreeView::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style = TVS_HASBUTTONS |
TVS_HASLINES |
TVS_LINESATROOT |
TVS_HASBUTTONS |
TVS_EDITLABELS |
WS_VISIBLE |
WS_CHILD |
WS_BORDER ;
return CTreeView::PreCreateWindow(cs);
}
ReplyGetApp()
Posted by Legacy on 02/17/1999 12:00amOriginally posted by: Song Wang
implement GetApp() in each view is not a good idea.
it is better to define a macro in "stdafx.h" like
#define GetApp() ( (CMyApp*)AfxGetApp() )
Reply