Drag and drop between views in a splitter window

Environment: ******-->

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:00am

    Originally 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:00am

    Originally 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

    Reply
  • Excellent

    Posted by Legacy on 07/02/2002 12:00am

    Originally posted by: Benjamins

    Great work..

    the explanation is simple & clear...thanks

    Reply
  • Tree View without an image list

    Posted by Legacy on 11/27/2001 12:00am

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

    Reply
  • not working using TVN_BEGINDRAG

    Posted by Legacy on 09/12/2001 12:00am

    Originally posted by: Hui

    My program will failed in BeginDrag if using TVN_BEGINDRAG in Treeview, can anyone help?

    Thanks!

    Reply
  • Typo T=L

    Posted by Legacy on 08/11/1999 12:00am

    Originally 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!

    Reply
  • Problem: message LVN_BEGINDRAG in TreeView not generated

    Posted by Legacy on 05/28/1999 12:00am

    Originally 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);
    }


    Reply
  • GetApp()

    Posted by Legacy on 02/17/1999 12:00am

    Originally 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
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • Best-in-Class organizations execute on a strategy that supports the multi-channel nature of customer requests. These leading organizations do not just open up their service infrastructures to accommodate new channels, but also empower their teams to deliver an effective and consistent experience regardless of the channel selected by the customer. This document will highlight the key business capabilities that support a Best-in-Class customer engagement strategy.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds