Drag and Drop ListBox Items Using OLE

OLE has made drag and drop a piece of cake. It allows two unrelated applications to exchange data in a format that they can both understand, with the help of Clipboard formats. Using OLE for drag and drop is a relatively simple task to accomplish. By using MFC, there really isn’t much to do but create the appropriate objects and call their appropriate methods. There are three MFC classes that are involved in a drag and drop operation. These classes are COleDataSource, COleDropSource, and COleDropTarget.

For a discussion about how to accomplish the same tasks without using OLE, please refer to this article.

Background

Here is a quick explanation of what each of these classes do. COleDataSource is the class that holds the data that will be transferred from the source to the destination. COleDropSource is a relatively small class that gives visual feedback during the drag and drop operation. And finally, COleDropTarget is the class that handles everything on the destination side of things. In reality, most people will only use COleDataSource and COleDropTarget, because most people don’t need to change behavior of COleDropSource, and it is automatically created by COleDataSource.

The drag and drop operation consists of the source object creating a COleDataSource, attaching some data to it, and calling the DoDragDrop method of the COleDataSource object. The destination object would have to implement a COleDropTarget class. The COleDropTarget class has a handful of virtual functions that get called during the operation; OnDragEnter, OnDragOver, OnDragLeave, and OnDrop are the most important ones. These methods must be overwritten so that you can tell the system what to do.

Take a look at the source for a minute. Here, you are going to create a COleDataSource and attach some text to it.


void CMyDragAndDropWnd::StartDrag()
{
//create the COleDataSource, and attach the data to it
COleDataSource DataSource;

//create a chunck of memory that will hold “This is a test”
//it is 15 chars in length
HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE,15);
char *pChar = (char *)GlobalLock(hData);
strcpy(pChar,”This is a test”);
GlobalUnlock(hData);

if (hData)
{
//attach the data to the COleDataSource Object
DataSource.CacheGlobalData(CF_TEXT,hData);

//allow the user to drag it
DROPEFFECT DropEffect =
DataSource.DoDragDrop(GetDragItemEffects(m_DraggedIndex));

//Once DoDragDrop returns, we can check the return value
//stored in DropEffect to see what kind of dropping
//happened. Like move, copy, or shortcut.
}
}

The dropping side of things is a bit more involved than the drag part. You have to inherit from COleDropTarget and overwrite some methods. The window that wants to receive drops needs to have an object of the inherited class, and register itself with it by using the Register method. Look at what a simple class inherited from COleDropTarget would look like.


//this method is called when the Drag operation first enters the
//window attached to this COleDropTarget object
DROPEFFECT CMyDropTarget::OnDragEnter(CWnd* pWnd,COleDataObject*
pDataObject,DWORD dwKeyState,CPoint point)
{
//if the data is the kind we want
if (pDataObject->IsDataAvailable(CF_TEXT))
{
//here we can take bring the window to the top
//because what is attached to the COleDropTarget object is
//most likely a child window, you would probably have to
//send it a message so that it can activate its parent or
//something.
SendMessage(m_hWnd,…,…,…);

//we can handle copy and move of this data
return DROPEFFECT_COPY|DROPEFFECT_MOVE;
}
//we can’t handle this type of data
return DROPEFFECT_NONE;
}

DROPEFFECT CMyDropTarget::OnDragOver(CWnd* pWnd,COleDataObject*
pDataObject,DWORD dwKeyState,CPoint point)
{
//if the data is the kind we want
if (pDataObject->IsDataAvailable(CF_TEXT))
{
//we can handle copy and move of this data
return DROPEFFECT_COPY|DROPEFFECT_MOVE;
}

//we can’t handle this type of data
return DROPEFFECT_NONE;
}

void CMyDropTarget::OnDragLeave(CWnd* pWnd)
{
//we can use this method to do any kind of clean up
//in this case, we don’t have anything
}

BOOL CMyDropTarget::OnDrop(CWnd* pWnd,COleDataObject*
pDataObject,DROPEFFECT dropEffect,CPoint point)
{
BOOL Ret = FALSE;

//if it is a format that we handle
if (pDataObject->IsDataAvailable(CF_TEXT))
{
//grab the data
HGLOBAL hGlobal = pDataObject->GetGlobalData(m_cfFormat);

//Do something with the data
//here you would normally send a message back to the
//window registered to the COleDropTarget to tell it
//to do something with the data
SendMessage(m_hWnd,WM_DOSOMETHING,hGlobal,0);

Ret = TRUE;
}

return Ret;
}

That’s all there is to it. Now, look at my Drag and Drop ListBox example.

How It Works

This Listbox class demonstrates how to rearrange listbox items, and also drag and drop items between two listboxes utilizing OLE’s Drag and Drop functionality. The concept is very simple: Get the item that the user want’s to drag, create a COleDataSource object, attach data to it, and call COleDataSource.DoDragDrop(). On the receiving end of things, indicate where the drop will be when the user is dragging the item around by overriding the OnDragOver method of COleDropTarget, and finally insert the item in it’s new location once the user release the mouse button which calls OnDrop method of COleDropTarget.

To accomplish this task, I created a new class, COleDragAndDropListBox, that inherits from CListBox and COleDropTarget. I like this method better than the containment method because, this way, the COleDropTarget derived class doesn’t have to send messages back to the CListBox dervied class to get information and notify of a drop operation. (The multi-inheritance part is not allowed in VC++ 6.0 and older, so this will only work when compiled using the VC++ 7.0 compiler.)

You will start with the dragging part of the task. You need to catch three messages for your listbox window to do this: WM_LBUTTONDOWN, WM_MOUSEMOVE, and WM_LBUTTONUP.

The WM_LBUTTONDOWN handler method simply determines which item the user has selected.


void COLEDragAndDropListBox::OnLButtonDown(UINT nFlags, CPoint point)
{
__super::OnLButtonDown(nFlags, point);

//keep track of the item that was clicked on
//WM_MOUSEMOVE message is going to use that to create
//the COleDataSource
m_Interval = 0;
m_DropIndex = LB_ERR;
m_DraggedIndex = LB_ERR;

BOOL Outside;
int Index = ItemFromPoint(point,Outside);
if (Index != LB_ERR && !Outside)
{
m_DraggedIndex = Index;
SetCurSel(Index);
}
}

The WM_WMMOUSEMOVE handler’s only job is to create a COleDataSource, attach data to it, and call DoDragDrop. Because not everyone would simply transfer text from one listbox to another, the CDragAndDropListBox class has defined some virtual functions to get the available drop modes, getting data, and dropping items. When an event that needs something happens, one of these virtual methods is used to get the information it needs. OnMouseMove is the first one to use these virtuals. It calls GetData() to get the data that should be attached to the COleDataSource, and later, after the the call to DoDragDrop(), it calls RemoveItem() to remove the item if the operation was a move operation.


void COLEDragAndDropListBox::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_DraggedIndex != LB_ERR && (nFlags & MK_LBUTTON))
{
//create the COleDataSource, and attach the data to it
COleDataSource DataSource;
HGLOBAL hData = GetData(m_DraggedIndex);
if (hData)
{
//attach the data
DataSource.CacheGlobalData(m_cfFormat,hData);

//allow the user to drag it.
DROPEFFECT DropEffect =
DataSource.DoDragDrop(GetDragItemEffects(m_DraggedIndex));

//if the user wanted to move the item then delete it
//Only do this if it was dragged to another window
//OnDrop handles deleting a moved item within the same
//window
if (DropEffect & DROPEFFECT_MOVE && m_DraggedIndex !=
LB_ERR)
{
RemoveItem(m_DraggedIndex);
}
m_DraggedIndex = LB_ERR;
GetParent()->SendMessage(WM_COMMAND,MAKEWPARAM
(GetDlgCtrlID(),LBN_SELCHANGE),(LPARAM)CListBox::m_hWnd);
}
}

__super::OnMouseMove(nFlags, point);
}

More by Author

Must Read