Implementing windows Drag and Drop functionanlity

We use drag and drop operation number of times. We often drag a file from the source directory to the destination directory in Windows Explorer. However, Windows Explorer drag and drop can handle only files and directories. OLE drag and drop is more general. It is able to transfer any kind of data.

The drag and drop operation involves a drop source and a drop destination. The drop source creates a data object that encapsulates the data and makes its pointer available. The drop target retrieves the pointer to the data object and uses it to extract the data.

In MFC COleDropSource class provides an implementation of the drop source object and COleDropTarget class provides an implementation of the drop target object. However we do not need to instantiate COleDropSource object because COleDataSource class does it for us.

In this article we will write a program that allows to drag and drop items across the list controls.

Create a dialog-based application ‘dragdroplistctrl’. Add two list controls to the dialog template. From the ‘Properties’ dialog box of both the list controls select the ‘View’ as ‘Report’ and check the ‘Single selection’ check box.

Initialise the OLE libraries in InitInstance( ) function before displaying the dialog box as given below:

BOOL CDragdroplistctrlApp::InitInstance( )

{

        if ( !AfxOleInit( ) )

                return FALSE ;

 

        //App Wizard added code

        return TRUE ;

}

Insert a new class by selecting ‘Insert | New Class’. Enter the class name as DragDropList and select the base class as CListCtrl.

Add two variables m_list1 and m_list2 of type DragDropList to the CDragdroplistctrlDlg class through ClassView tab. Add the following code in CDragdroplistctrl::OnInitDialog( ) function.

BOOL CDragdroplistctrlDlg::OnInitDialog( )

{

        // AppWizard added code

        m_list1.SubclassDlgItem ( IDC_LIST1, this ) ;

        m_list2.SubclassDlgItem ( IDC_LIST2, this ) ;

       

        m_list1.InsertColumn ( 0, "Item1", LVCFMT_LEFT, 150 ) ;

        m_list2.InsertColumn ( 0, "Item1", LVCFMT_LEFT, 150 ) ;

        CString str = "" ;

        for ( int i = 0 ; i <>

        {

                str.Format ( "Item #%d", i ) ;

                m_list1.InsertItem ( i, str ) ;

        }

        for ( i = 0 ; i <>

        {

                str.Format ( "Item #%d", i ) ;

                m_list2.InsertItem ( i, str ) ;

        }

        m_list1.initialize( ) ;

        m_list2.initialize( ) ;

        return TRUE ;

}

Here, we have subclassed the list controls, inserted columns and added items in them. Next, we have called DragDropList::initialize( ) function. initialize( ) is the user-defined function that looks as shown below:

void DragDropList::initialize( )
{

       
BOOL success = m_droptarget.Register ( this ) ;
       
if ( !success )
               
AfxMessageBox ( "Ole Register Drop Target Failed" ) ;
}

To register a window as a valid drop target we must call COleDropTarget::Register( ) function. However, generally we derive a class from COleDropTarget to provide our own functionality and call Register( ) function of the derived class. We have derived a class ListDropTarget from COleDropTarget. Add a variable m_droptarget of type ListDropTarget through ClassView tab. Using this object we have registered the list controls as the drop targets. Insert the class ListDropTarget by selecting ‘Insert | New Class’. Select the ‘Class type’ as ‘Generic Class’. Enter the class name as ListDropTarget. In the ‘Derived From’ list box enter the base class name as COleDropTarget. Click the ‘OK’ button. In ’ListDropTarget.h’ file #include ‘afxole.h’ file for the COleDropTarget class.

The Drop Source

Whenever the user drags a list control item the list control gets a notification called LVN_BEGINDRAG. We have handled this notification. The OnBegindrag( ) handler is given below:

void DragDropList::OnBegindrag ( NMHDR* pNMHDR, LRESULT* pResult )

{

        NM_LISTVIEW* pNMListView = ( NM_LISTVIEW* ) pNMHDR ;

        int item = ( ( NM_LISTVIEW* ) pNMHDR ) -> iItem ;

        m_source = TRUE ;

        HANDLE hdata = ::GlobalAlloc ( GPTR, sizeof ( LV_ITEM ) ) ;

        LV_ITEM *lvi = ( LV_ITEM* ) ::GlobalLock ( hdata ) ;

        ::GlobalUnlock ( hdata ) ;

        char label[256] ;

        lvi -> mask  = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;

        lvi -> stateMask = LVIS_DROPHILITED | LVIS_FOCUSED | LVIS_SELECTED;

        lvi -> pszText = label ;

        lvi -> iItem = item;

        lvi -> cchTextMax = 255 ;

        GetItem ( lvi ) ;

        COleDataSource m_coledatasource ;

        m_coledatasource.CacheGlobalData ( CF_TEXT, hdata ) ;

 

        DROPEFFECT dropeffect = m_coledatasource.DoDragDrop (DROPEFFECT_COPY | DROPEFFECT_MOVE ) ;

         if ( ( dropeffect & DROPEFFECT_MOVE ) == DROPEFFECT_MOVE )

                DeleteItem ( item ) ;

 

        m_source = FALSE ;

        *pResult = 0;

}

Here, firstly the index of the item being dragged is stored in an integer variable item. We have initialised a BOOL variable m_source to TRUE. m_source is a data member of DragDropList class which is being initialised to FALSE in the constructor. We will see the use of this variable later on. We have used ::GlobalAlloc( ) API function to create a global memory block as a storage medium. The GlobalAlloc( ) function returns a handle to the memory. ::GlobalLock( ) function takes this handle and returns a pointer to the memory block. We have stored this pointer in LV_ITEM*. ::GlobalUnlock( ) function unlocks the memory. In the pointer lvi we have retrieved the list control item specified by item. The data is then handed over to the COleDataSource object by calling COleDataSource::CacheGlobalData( ) function. The actual process of drag and drop starts when COleDataSource::DoDragDrop( ) function is called. The first parameter passed to DoDragDrop( ) specifies the action to be taken when the item is dragged and dropped. We have specified the ‘Copy’ or ‘Move’ operation. So, when user drags the item from one list control to other it will get moved and if Ctrl key is kept depressed while dragging the item the item will get copied to the other control. The DoDragDrop( ) function returns the drop effect, i.e., whether the item has been moved or copied.

The Drop Target

The COleDropTarget class provides few overridables that are listed below:

Function

Description

DragEnter( )

Called when cursor enters the drop target window

DragOver( )

Called as the cursor moves over the drop target window

DragLeave( )

Called when cursor leaves the drop target window

Drop( )

Called when a drop occurs

The COleDropTarget derived class (ListDropTarget in our case) must override all or the combination of these functions. We have overridden all the functions except DragLeave( ). Add the declarations of the functions in ‘ListDropTarget.h’ file as given below:

DROPEFFECT OnDragEnter ( CWnd* pWnd, COleDataObject* pDataObject,DWORD dwKeyState, CPoint point ) ;

DROPEFFECT OnDragOver ( CWnd* pWnd, COleDataObject* pDataObject,DWORD dwKeyState, CPoint point ) ;

BOOL OnDrop ( CWnd* pWnd, COleDataObject* pDataObject,DROPEFFECT dropEffect, CPoint point ) ;

Add the definitions to ‘ListDropTarget.cpp’ file as given below:

DROPEFFECT ListDropTarget::OnDragEnter ( CWnd* pWnd,COleDataObject* pDataObject, DWORD                                                dwKeyState, CPoint point )
{

}

DROPEFFECT ListDropTarget::OnDragOver ( CWnd* pWnd,COleDataObject* pDataObject, DWORD                                                dwKeyState, CPoint point )
{

}

BOOL ListDropTarget::OnDrop ( CWnd* pWnd,COleDataObject*  pDataObject,DROPEFFECT dropEffect, CPoint point )
{

}

Note that since ListDropTarget is a generic class we need to add these functions manually.
Add the code shown below in OnDragEnter( ) function.

DROPEFFECT ListDropTarget::OnDragEnter ( CWnd* pWnd,COleDataObject* pDataObject, DWORD                                                dwKeyState, CPoint point )
{

        if ( ( dwKeyState & MK_CONTROL ) == MK_CONTROL )

                return DROPEFFECT_COPY ;

        else

                return DROPEFFECT_MOVE ;
}

Add the same code as given in OnDragEnter( ), in the OnDragOver( ) function.

The parameters passed to these functions are same. The first parameter is the pointer to the window the cursor is entering. The second parameter is the pointer to the data object containing the data that can be dropped. (This pointer actually points to the global memory block that is allocated in the DragDropList::OnBegindrag( ) function). The third parameter contains the state of the modifier keys like Ctrl, Shift etc. The last parameter is the current location of the cursor in client coordinates. The OnDragEnter( ) and OnDragOver( ) functions return the drop effect flag. If the Ctrl key is pressed we have returned DROPEFFECT_COPY flag otherwise we have returned the DROPEFFECT_MOVE flag.

The actual drop operation is performed in OnDrop( ) function. The OnDrop( ) function is given below:

BOOL ListDropTarget::OnDrop ( CWnd* pWnd, COleDataObject*pDataObject, DROPEFFECT dropEffect, CPoint point )

{

        DragDropList* plist = ( DragDropList* ) pWnd ;

        int item ;

        if ( plist -> GetItemCount( ) == 0 )

                item = 0 ;

        else

        {

                UINT flags ;

                point.y += 5 ;

                item = plist -> HitTest ( point, &flags ) ;

                if ( item == -1 )

                        item = plist -> GetItemCount( ) ;

                else

                        item++ ; // drop dragged item after the selected item

        }

        HANDLE hglobal = pDataObject -> GetGlobalData ( CF_TEXT ) ;

        LV_ITEM *litem = ( LV_ITEM* ) GlobalLock ( hglobal ) ; 

        litem -> iItem = item ;

        if ( plist -> m_source )

        {

                AfxMessageBox ( "An item can't be dropped onto itself" ) ;

                return FALSE ;

        }

        else

                plist -> InsertItem ( litem ) ;

        GlobalUnlock ( hGlobal ) ;

        return TRUE ;

}

To the OnDrop( ) function too, the pointer to the target window and the data object get passed as first two parameters. The third parameter is the effect user chose as the drop operation and last parameter is the location of cursor. Here, firstly we have ascertained the position or index where the item is to be dropped. CListCtrl::HitTest( ) function returns the index of the item specified by the first parameter, –1 otherwise. If HitTest( ) function returns –1 we will drop the item at the end of the list. Next we have retrieved the data by calling CDataObject::GetGlobalData( ) function. The handle returned by GetGlobalData( ) is then passed to GlobalLock( ) function to obtain pointer to the data. We have stored the pointer in LV_ITEM*. We have stored the index where the item is to be dropped in the iItem element of LV_ITEM structure. Here we have made use of the m_source variable to check whether the source and target are the same. In DragDropList::OnBegindrag( ) function we had initialised m_source of the source window to TRUE. Since pointer of the target window is passed to OnDrop( ) we can check whether the m_source of target window is TRUE or not. If it is then source and target are the same. If the source and target windows are same we have cancelled the drop operation by returning FALSE. Otherwise we have inserted the item in the target window by calling InsertItem( ) member function of CListCtrl class. Finally we have unlocked the memory.

If you run the program you will see two list controls having few items. You can transfer the items across them by the drag and drop operation

Download

No comments: