Drop Source

OLE Drag and Drop

Updated 6 Dec 2006

Many thanks to Davide Chiodi from Italy who has very kindly converted the drop-source code into a Pure C implementation

Welcome to the fifth article in the "OLE Drag and Drop" tutorial series! We are almost at the final stages in our OLE drag and drop implementation. The only thing left to do is implement the IDropSource and IDropTarget interfaces. Once we have done this we will be ready to add drag and drop to any application..

The aim of this article is to implement a simple application which can be used as the source of a drag-drop operation. It won't be able to accept any drag-drop data, but this doesn't matter because we can use any normal Windows program which supports text drag+drop (such as WordPad) for testing. The application will basically be a normal Windows EDIT control, which will be subclassed and drag-support added.

The details of this subclassing won't be discussed in this tutorial, but the source-code download clearly demonstrates how to perform this simple task.

Become a "Drop Source"

It is very simple to initiate a drag-and-drop operation. Calling the DoDragDrop API call is sufficient.

WINOLEAPI DoDragDrop(
   IDataObject  * pDataObject,   // Pointer to the data object
   IDropSource  * pDropSource,   // Pointer to the source
   DWORD          dwOKEffect,    // Effects allowed by the source
   DWORD        * pdwEffect      // Pointer to effects on the source
   );

As soon as this API is called, the OLE runtime takes over and handles all the necessary mouse and keyboard windows messages on behalf of your application, so you basically release control to OLE once you call this function.

The first two parameters to DoDragDrop are COM interfaces. One is an IDataObject - we have already implemented this in a previous tutorial.

The third parameter is a DWORD value which specifies (in the form of a bit-mask) the "effects" that are allowed by the source (i.e. us). These effects are taken from the DROPEFFECT_xxx values, and will usually be and combination of DROPEFFECT_MOVE and DROPEFFECT_COPY. If we wanted to allow only copying data from our source, then we would specify just DROPEFFECT_COPY, and the opposite is true as well.

The last parameter is a pointer to a DWORD value. This value is accessed when DoDragDrop returns, and will contain the "effect" or action that OLE wants the source to perform - i.e. did the user elect to move or copy the data?

The code to perform a drag-and-drop operation is usually split into three steps. First of all though we need to write a small utility function called StringToHandle, which will convert a normal char* string into HGLOBAL form so we can use it with OLE:

HANDLE StringToHandle(char *szText, int nTextLen)
{
    void  *ptr;
	
    // if text length is -1 then treat as a nul-terminated string
    if(nTextLen == -1)
        nTextLen = lstrlen(szText);
    
    // allocate and lock a global memory buffer. Make it fixed
    // data so we don't have to use GlobalLock
    ptr = (void *)GlobalAlloc(GMEM_FIXED, nTextLen + 1);
	
    // copy the string into the buffer
    memcpy(ptr, szText, nTextLen);
    ptr[nTextLen] = '\0';
	
    return ptr;
}

The StringToHandle function has absolutely no error checking so it's up to you to add this on your own. The next step is to prepare some data to use in the drag-and-drop operation:

FORMATETC fmtetc = 
{ 
    CF_TEXT,            // we will be dropping some text
    0, 
    DVASPECT_CONTENT, 
    -1, 
    TYMED_HGLOBAL       // stored as a HGLOBAL
};

STGMEDIUM stgmed = 
{ 
    TYMED_HGLOBAL,      // the data goes here
    { 0 }, 
    0 
};

// Create a HGLOBAL inside the storage medium
stgmed.hGlobal = StringToHandle("Hello, World", -1);

Next up is the creation of the two COM interfaces required for drag and drop - IDropSource and IDataObject. We have already implemented CreateDataObject in a previous tutorial, and CreateDropSource will be implemented shortly!

IDropSource *pDropSource;
IDataObject *pDataObject;

CreateDropSource(&pDropSource);
CreateDataObject(&pDataObject, &fmtetc, &stgmed, 1);

The call to DoDragDrop can be made once the IDataObject and IDropSource have been successfully created.

DWORD dwResult;

// do the drag-drop!
dwResult = DoDragDrop(pDataObject, pDropSource, DROPEFFECT_COPY, &dwEffect);

// finished. Check the return values to see if we need to do anything else
if(dwResult == DRAGDROP_S_DROP)
{
    if(dwEffect == DROPEFFECT_MOVE)
    {
        // remove the data we just dropped from active document
    }
}

The very last thing to do is cleanup any resources we used. First of all we remove the last reference to the two COM interfaces, after which they will be automatically deleted. And lastly, we delete the HGLOBAL memory buffer that contains our text.

// release the COM interfaces
pDropSource->Release();
pDataObject->Release();

ReleaseStgMedium(&stgmed);

When to call DoDragDrop

Knowing how to initiate a drag-and-drop operation is all very well, but it is important to understand where to integrate the above code into your applications.

Because drag & drop is mouse-based, it is customary for an application to initiate it whilst processing normal Windows mouse messages. If you take the time to play with some drag-and-drop enabled applications (such as WordPad) you will observe the following behaviour for the RichEdit control:

  1. When the mouse moves over a selected area of text it's cursor shape changes to an arrow.
  2. When the left button is pressed, the selection is not removed. Instead an internal state is set to indicate that a drag-drop operation might be about to start.
  3. When the mouse is first moved (and the internal state indicates that the left button is currently being held down inside a selected area of text), the drag and drop operation starts.
  4. At this point, OLE takes over and handles all further mouse messages until the operation is complete.
  5. However, if the left button is released and the mouse didn't move at all, it is customary for the RichEdit's selection to be cleared and the text-caret positioned under the mouse.

This behaviour is quite simple to implement in C or C++. For our subclassed EDIT control it will look something like this:

case WM_LBUTTONDOWN:

    if(MouseInSelection(hwndEdit))
    {
        fMouseDown = TRUE;
        return 0;
    }
    break;

case WM_MOUSEMOUVE:

    if(fMouseDown == TRUE)
    {
        DoDragDrop(...);
    }

    fMouseDown = FALSE;
    break;

case WM_LBUTTONUP:
    fMouseDown = FALSE;
    break;

The IDropSource Interface

IDropSource is the simplest of all the drag-and-drop interfaces. Excluding the IUnknown functions, it has only two functions that need to implemented.

IDropSource Methods Description
QueryContinueDrag Determines whether a drag-and-drop operation should be continued, cancelled or completed, based on the state of the mouse buttons and the <Escape>, <Control> and <Shift> keys.
GiveFeedback Provides a method for the source of the drag-drop to give visual feedback based on the state of the modifier keys listed above (i.e. mouse buttons, escape, control etc).

Both of these functions are called by the COM/OLE runtime whenever the state of the drag-and-drop modifier keys are changed. Very little work needs to be done to implement this interface - in fact, far more coding has already been performed just to prepare for the drag-drop!

Implementing IDropSource

Again we can use a single source-file to implement a drop-source. Inside dropsource.cpp will be the class declaration for our drop-source object. This is what you need to type in:

class CDropSource : public IDropSource
{
public:
    //
    // IUnknown members
    //
    HRESULT __stdcall QueryInterface    (REFIID iid, void ** ppvObject);
    ULONG   __stdcall AddRef            (void);
    ULONG   __stdcall Release           (void);

    //
    // IDropSource members
    //
    HRESULT __stdcall QueryContinueDrag (BOOL fEscapePressed, DWORD grfKeyState);
    HRESULT __stdcall GiveFeedback      (DWORD dwEffect);

    //
    // Constructor / Destructor
    //
    CDropSource();
    ~CDropSource();
private:

    //
    // private members and functions
    //
    LONG	   m_lRefCount;
};

The constructor for this class does not perform any task other than initializing the object's reference count.

IDropSource::QueryContinueDrag

Below is the definition for the IDropSource::QueryContinueDrag function:

HRESULT QueryContinueDrag(
   BOOL   fEscapePressed,        // Is the <Escape> key being pressed?
   DWORD  grfKeyState,           // Current state of keyboard modifier keys
   );

This function can return one of three values:

  • S_OK The drag operation should continue. This result occurs if no errors are detected, the mouse button starting the drag-and-drop operation has not been released, and the Esc key has not been detected.
  • DRAGDROP_S_DROP The drop operation should occur completing the drag operation. This result occurs if grfKeyState indicates that the key that started the drag-and-drop operation has been released.
  • DRAGDROP_S_CANCEL The drag operation should be canceled with no drop operation occurring. This result occurs if fEscapePressed is TRUE, indicating the Esc key has been pressed.

Is is customary in COM for the following two behaviours to be observed whilst in a drag-and-drop operation.

  1. When the Escape key is pressed, cancel the drag-drop operation.
  2. When the Left mouse button is released, the drop should be performed.

Adhering to these guidelines results in the following implementation of IDropSource::QueryContinueDrag:

HRESULT __stdcall CDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
{
    // if the Escape key has been pressed since the last call, cancel the drop
    if(fEscapePressed == TRUE)
        return DRAGDROP_S_CANCEL;	

    // if the LeftMouse button has been released, then do the drop!
    if((grfKeyState & MK_LBUTTON) == 0)
        return DRAGDROP_S_DROP;

    // continue with the drag-drop
    return S_OK;
}

IDropSource::GiveFeedback

The IDropSource::GiveFeedback function is usually different for every application, because no one application will be the same. However, unless we are providing graphical feedback effects whilst an object is being dragged from our application, our implementation of GiveFeedback is extremely simple.

HRESULT __stdcall CDropSource::GiveFeedback(DWORD dwEffect)
{    
    return DRAGDROP_S_USEDEFAULTCURSORS;
}

The dwEffect parameter (which tells us which mouse buttons are pressed and which of the keyboard modifiers are in use) can be ignored for alot of drag-and-drop applications. By simply returning the value DRAGDROP_S_USEDEFAULTCURSORS we can instruct COM to update the mouse cursor automatically whenever the modifiers change.

Of course, we could inspect the DROPEFFECT_xxx flags inside dwEffect, do some painting on our source-window and return S_OK instead, but why bother?

Coming up in Part 6 - IDropTarget

With the drop-source out of the way, we are now ready to start implementing a drop target interface. This is the last component needed for OLE drag and drop.