Drag And Drop between Window Controls

Environment: The only restriction is that you have IE4 (with at least SP2)

Introduction

Many people have been asking for Drag & Drop between windows controls. After reading an article on the CodeGuru site, and trying the functionality of MFC for drag and drop, I decided to implement it in to one of my applications.

The MFC implementation is a very good example of the quality of code that is written after after lengthy heavy technical discussion. In other words, it's bad. It has too much overhead on the OO site, and in the end it doesn4t work (and when it does, it's a miracle). If you don't agree with this statement, step through the MFC code to see what I mean.

This is also a rather weird thing. Microsoft has been promoting Drag&Drop API (D&D API) as one of the major things to implement in a Windows application. Yet, Microsoft doesn't even support it in most if its own applications. Support for it is good on the COM side of things, but how do you implement Drag & Drop from a standard Win32 or MFC application?

I have learned a lot from CodeGuru over the years. So, here's is something back. The secret lies buried in a COM interface called IDropTarget.

How does it all work?

So how does it all work? Well, what we basically do is register the windows we want to use in drag and drop operations with the OS and the D&D API. When a modal drag and drop operation is present, the D&D API will scan that list and if it finds that the cursor is in one the the windows registered, it will notify that window by the interface object IDropTarget. That object is then responsible for handling the result of the drag and drop.

So we link the object up with the window [handle] that will support a drop operation, and call the standard MFC drop API COleDataSource::DoDragDrop(). Below is some documentation on how to do this, the sample distributed with this article shows how to drag and drop text from a listbox, a combobox, and a button between each other. Implementing this for other controls is not that hard.

IDropTarget

IDropTarget is defined in oleidl.h, one of the basic OLE include files. We will implement this interface in a baseclass called ECOleMemDropTarget. This class will handle the calls send by the Drag and Drop API. The class will look for dropped data and check if the type of data passed to it, and registered by the D&D API on the clipboard, is correct. If that is the case, it will let us manipulate it.

The definition for ECOleMemDropTarget looks like this.


interface ECOleMemDropTarget : public IDropTarget
{
public:
ECOleMemDropTarget();
~ECOleMemDropTarget();

// basic IUnknown stuff
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject); 
ULONG STDMETHODCALLTYPE AddRef(void); 
ULONG STDMETHODCALLTYPE Release(void); 

IDropTarget stuff
HRESULT STDMETHODCALLTYPE DragEnter(struct IDataObject *,unsigned long,struct _POINTL,unsigned long *); 
HRESULT STDMETHODCALLTYPE DragOver(unsigned long,struct _POINTL,unsigned long *); 
HRESULT STDMETHODCALLTYPE DragLeave(void);
HRESULT STDMETHODCALLTYPE Drop(struct IDataObject *,unsigned long,struct _POINTL,unsigned long *);

// called by parents
BOOL Register(CWnd* pWnd, UINT pDataType);
void Revoke();

// call parent we have goodies
virtual void GotDrop(void);
virtual DWORD GotDrag(void);
virtual void GotLeave(void);
virtual DWORD GotEnter(void);
public:
BYTE *m_Data;

CPoint m_DropPoint;

DWORD m_KeyState;

protected:
CWnd* m_DropTargetWnd;

UINT m_RegisterType;

DWORD m_dwRefCount;
};
It inherits from IDropTarget, and implements the needed methods from IUnknown as all COM interface derived classes, and the m_dwRefCount datamember needed for garbage collection/reference counting.

DragEnter, DragOver, DragLeave and Drop are interface methods of IDropTarget.These are the methods that will get called by the D&D API when a drag and drop operation is in progress above our window. We don4t define these as pure virtual functions. Some of our parent will not be interested in what we have to say (yeah, C++ is really like the world out there ;-)), and we dont want to put the burden on them of writing some stub code.

The public datamembers contain data on a D&D call in the works: m_DropPoint contains the screen coordinates of the point of the drop, and m_KeyState the state of some windows key when the user released the mouse. You could edit the functionality of the class so that these contain valid data during the D&D operation.

The code of our class

First are the Register and Revoke members. These will set up a window as a drag and drop client.

BOOL ECOleMemDropTarget::Register(CWnd* pWnd, UINT pDataType)
{
if(NULL == pWnd)
return E_FAIL;

if(0L == pDataType)
return E_FAIL;

// keep
m_DropTargetWnd = pWnd;
m_RegisterType = pDataType;

// this is ok, we have it
DWORD hRes = ::RegisterDragDrop(m_DropTargetWnd->m_hWnd, this);
if(SUCCEEDED(hRes))
return TRUE;

// wont accept data now
return FALSE;
}
We pass the MFC window handle for our window, and the datatype of the data that will be registered on the clipboard and in what we are interested. The code then calls Windows RegisterDragDrop, and that passes our window WIN32 handle to the OS as a valid target for Drag&Drop. Note that we must already have a window handle here, so this call will not succeed from the constructor of window classes. Mission part one ok. Revoke just calls Windows RevokeDragDrop(). This removes the window handle again from the OS D&D API.

Your mission, should you...

To make our code a bit more generic, I derived a class from ECOleMemDropTarget called ECMaterialDropText. Thats a basic class that will support drag and drop for text, and that will handle the text data type from the clipboard.

We need to connect an instance of this class to a window control. The easiest thing to do is just inherit our own custom controls for this from MFC. Lets do this for a button. We bring up the classwizard, press "Add class... new", enter the name ECButton (our whatever you want to call it), and select CButton as a baseclass. OK, and we have our own custom button class. God, don4t we all love that wizard.

We will put a datamember instance of ECOleMemDropTarget in our button class: ECMaterialDropText *m_TargetDrop. In the constructor we create the memory for it. We call the Register() function of the member. Because MFC doesn4t call any Create members, we will do this in a small methode called InitDrag(). That basically just calls our Register() of ECMaterialDropText.

Next, we add handlers for mousebuttons, mousemove4s and a timer function. We use the left mouse button, you could use the right, middle or the n4th mouse button. OnLButtonDown keeps our coordinates and starts the timer, OnLButtonUp stops the timer. The OnTimer method checks if the mouse cursor is still in the window.

Why do we do all this? Well, we only want to start a drag and drop if the user has pressed and moves the mouse in the window (I choose a distance of 10 pixels). The timer checks if that is the case, and if the mouse is outside the button (and a drag and drop not started), it cancels the action. This also prevents a user from pressing the mouse button outside of our window control, and the moving and releasing inside of the control. Ah users, difficult people. The world would be so good without them :-)

The real action for the drag and drop is started in OnMouseMove. If we did the minimum distance we take the text from the control, and pump it to the COleDataSource to start a drag. That part is described in other good articles in the Clipboard area on this great site. And DoDragDrop() starts our action.

Action!

The Drag & Drop API will now modally begin scanning our mouse moves and the windows the mouse is in. If it arrives in our button or one of the registered other drag and drop windows, the API will call the IDropTarget::DragEnter member. We call GotEnter() for our parents, in our case for ECMaterialDropText::GotEnter().

DragEnter must set a constant giving GUI feedback on the action the user can do. For our code, we just call DROPEFFECT_LINK, one of the constants for drag and drop. I suggest you look them up in the MFC docs.

When we move inside a registered D&D window, the D&D API will call IDropTarget::DragOver(), and we call GotDrag(); My implementation from the code does some checking for runtime classes and depending on the window we are in sends some GUI feedback. Check out the implementation in ECMaterialDropText.cpp. Note that Windows itself shows the classical not valid drop cursor if the window the cursor is over is not registered. We also return one of the constants to give GUI feedback for the mouse cursor.

Last, if the user releases the mouse button in a registered window, we get a "notification" call on IDropTarget::Drop(). The code there checks if the data that the D&D API placed in the clipboard is OK for us, and if so calls GotDrop(). We retrieve the text data, and set it in our control.

The D&D API on exit of Drop() returns after the modal loop of DoDragDrop(); and we free the clipboard data. Mission accomplished!

Remarks

Most of the code in the sample is really cut and paste, added after we derived controls with the classwizard. All the mouse button and move stuff is the same, you could put this in a generic class and inherit from it (if you like multiple inheritance).

Dont forget the all important AfxOleInit() in your code. If you dont add that one the Register function will fail.

Error feedback for the code could be better. Hey, I wrote this sample up in an hour or two...

The sample

The sample shows all I wrote down above. As they say, one code snippet shows more then 100000 lines of documentation. If it seems overwhelming at first, just dig in. For reading on the COM stuff, I would suggest a copy of Don Box excellent COM book.

If you have questions, gimme a mail at erik.vangrunderbeeck@village.uunet.be. But do keep in mind that to answer my mail on a regular base, I have to dig through 300 emails a day, and that means 2 weeks between receipt and reply (unless you send mail like "I would like you to have my son..."). Nah. My wife wouldnt like that ;-)

Have fun and write some great code

Downloads

Download demo project - 31 Kb