Drag And Drop between Window Controls

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

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


Comments

  • RichEdit

    Posted by Legacy on 04/22/2002 12:00am

    Originally posted by: Reza

    Great job. I wonder why can not I use RichEditCtrl for drag and drop. Any help?

    Reply
  • How to drag and drop file and folder

    Posted by Legacy on 08/15/2000 12:00am

    Originally posted by: kumars

    Can someone give me some guide on how to do this on a DirTree with Files and Folders.

    Thank you

    Suresh

    Reply
  • very clear briefing of your sample code!!!

    Posted by Legacy on 04/26/2000 12:00am

    Originally posted by: sirigudi

    Thanks a lot for your sample code & the explanation along with it.
    It was of great help for me.
    Sirigudi.

    Reply
  • sample's struct is very clear.

    Posted by Legacy on 03/02/2000 12:00am

    Originally posted by: motse

    thanks , i get very clear about this samples. it's a very
    good MFC design structure.

    Reply
  • Interesting side effect - Part 2

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

    Originally posted by: Patrick Smout

    Hi,
    
    

    The problem that the listbox is filled up with numerous copies when one of its own strings is dropped on it, is caused by the running timer. To solve this problem it is sufficient to kill the timer once a drag and drop operation is initiated. You can make the same modifications to the code for the other controls. BTW, the solution for this problem submitted by Hilda I.Figueroa is unsafe because CacheGlobalData() uses hMem after the memory is freed (does it work anyway?).

    Best regards,

    Patrick

    // *****************************************************************
    // OnMouseMove
    // *****************************************************************
    void CDragDropListBox::OnMouseMove(UINT nFlags, CPoint point)
    {
    if(m_TimerID > 0)
    {
    // check if we really moved enough
    int iX = m_StartPoint.x - point.x;
    int iY = m_StartPoint.y - point.y;
    if((iX*iX + iY*iY) > MINIMUM_MOVE_SQUARE)
    {
    // Insert next 2 lines
    KillTimer(m_TimerID);
    m_TimerID = 0;

    COleDataSource* pSource = new COleDataSource();
    if(pSource)
    {

    Reply
  • Interesting Side Effect

    Posted by Legacy on 10/25/1999 12:00am

    Originally posted by: Hilda I. Figueroa

    Hi!

    This is a nice example. Thank you!
    FYI -
    I did notice an interesting side effect. After I had dragged and dropped the 3 strings from the combo box to the list box, I decided to drag 1 of the strings, within the same list box, to the top of the list. This caused the list box to disappear momentarily, and then a scroll bar appeared and
    there were numerous copies of the same text string displayed.
    To fix this problem:
    In the member function:
    void CDragDropListBox::OnMouseMove(UINT nFlags, CPoint point)
    .
    .
    .
    /* After this statement */
    HGLOBAL hMem = sf.Detach();

    /* ADD THIS SEGMENT TO PREVENT INTERESTING BEHAVIOR */
    #if _MFC_VER <= 0x0600

    ::GlobalUnlock(hMem);
    GlobalFree(hMem);
    #endif

    /* Before this statement */
    if (!hMem)
    return;

    Reply
  • Destructor must be virtual

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

    Originally posted by: Erik Van Grunderbeeck

    Oops: the destructor of the class ECOleMemDropTarget must be virtual (so the classdefinition should read:)

    class ECOleMemDropTarget
    {
    ECOleMemDropTarget();
    virtual ECOleMemDropTarget();

    .....

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • In order for IT service providers to succeed, it's paramount that they find a competitive advantage and continually develop new ways to find additional revenue streams. IT service providers need to be able to do it all for their clients – from managing entire technology infrastructures to responding quickly to a multitude of end-user needs. With a growing number of issues to resolve and limited technicians at hand, how can IT service providers operate efficiently while providing top-notch service …

  • Many enterprises are working with an IT architecture that's evolved over time. As business needs evolve, IT must decide whether to modernize incrementally, or all at once. Each approach has its benefits and drawbacks. Identity Management is key to modernizing IT; it plays a crucial role in migrating to cloud apps like Office 365 or HR information systems, building web and mobile apps, and opening developer access to business systems. Read how Okta's modern approach to identity management helps business lower …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date