Drop Source
8 minute read •
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:
- When the mouse moves over a selected area of text it’s cursor shape changes to an arrow.
- 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.
- 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.
- At this point, OLE takes over and handles all further mouse messages until the operation is complete.
- 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 ifgrfKeyState
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 iffEscapePressed
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.
- When the
Escape
key is pressed, cancel the drag-drop operation. - 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.