Implement a Drop Target Object with IDropTarget

The next step for a target implementation is to implement the drop target object. In Cosmo, this is CDropTarget, found in COSMO.H and DROPTGT.CPP. This object maintains a backpointer to the CCosmoDoc object because this object will be associated with the document window. In addition, CDropTarget maintains an IDataObject pointer, specifically the one it receives in IDropTarget::DragEnter. This lets Cosmo examine the data object during calls to DragOver, which do not include a data object as an argument. Thus, we need to remember the data object in DragEnter.

IDropTarget::DragEnter

This function is called whenever the mouse moves into a registered target window. It receives the following arguments:

Argument

Description

pIDataSource

(IDataObject *): A pointer to the source's data object (marshaled as necessary).

grfKeyState

(DWORD): MK_* flags describing the current state of the Ctrl, Alt, and Shift keys as well as each mouse button (as with IDropSource::QueryContinueDrag).

pt

(POINTL): The current location of the mouse, in screen coordinates.

pdwEffect

(DWORD *): A pointer to a DWORD containing the allowable effects as specified by the source. On output, the target stores the effect a drop would have with the mouse at the current position in pt.


With these arguments, you implement DragEnter as follows:

Call pIDataSource->EnumFormatEtc and pIDataSource->QueryGetData to determine whether the source has usable data, generally the same data that the target would be able to paste from the clipboard. If no usable data is found, store DROPEFFECT_NONE in *pdwEffect and return NOERROR.

Determine whether pt is a suitable drop point. If not, store DROPEFFECT_NONE in *pdwEffect and return NOERROR.

If pt is a suitable drop point, determine what effect will occur and store that flag in *pdwEffect.

If you want to access pIDataSource in DragOver, save the pointer and call its AddRef.

Provide user feedback and return NOERROR.

You'll notice that DragEnter generally returns NOERROR, even for unusable data. OLE will always call DragEnter when the mouse moves into your window and will continue to call DragOver and eventually DragLeave, even if you continually return DROPEFFECT_NONE in *pdwEffect. Saying "no drop" doesn't prevent additional DragOver calls. Here is Cosmo's DragEnter:


STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
HWND hWnd;

m_pIDataObject=NULL;

if (!m_pDoc->FQueryPasteFromData(pIDataSource))
{
*pdwEffect=DROPEFFECT_NONE;
return NOERROR;
}

*pdwEffect=DROPEFFECT_MOVE;

if (grfKeyState & MK_CONTROL)
*pdwEffect=DROPEFFECT_COPY;

m_pIDataObject=pIDataSource;
m_pIDataObject->AddRef();

hWnd=m_pDoc->Window();
BringWindowToTop(hWnd);
UpdateWindow(hWnd);
m_pDoc->DropSelectTargetWindow();

return NOERROR;
}

In Chapter 12, we gave Cosmo a suitable "query paste from data" function: CCosmoDoc::FQueryPasteFromData. Again, a function such as this is very useful and centralizes clipboard, drag-and-drop, and compound document data transfers. We can see that step 1 in the preceding code calls this function and returns DROPEFFECT_NONE if it fails. If there is usable data, Cosmo continues at step 3 by storing DROPEFFECT_MOVE or DROPEFFECT_COPY, depending on the state of the Ctrl key. If Cosmo supported linking (which it does not), it would check the state of the Shift and Ctrl keys and store DROPEFFECT_LINK if the Shift key were pressed.

But why did we skip step 2? Well, Cosmo accepts a drop anywhere in its document window—including the Polyline window—so it's not picky about the drop point. Also note that DoDragDrop calls the Windows API function WindowFromPoint to find the drop target. If the window is a child window but is not registered for drag and drop, OLE checks its parent window to see whether the parent is a target. If not, it will continue up the chain, checking each parent window.

That leaves us with step 4, in which we simply store the pIDataSource pointer in the drop target object (calling its AddRef like a good citizen), and then step 5, in which we give some visual feedback about what might happen if a drop occurred. We designed CCosmoDoc::DropSelectTargetWindow for this purpose in Cosmo, so we call that function here.

Before this, however, the calls to BringWindowToTop and UpdateWindow ensure that the target window is fully visible. Using BringWindowToTop has a nice effect: when you drag across document windows, Cosmo will bring the current target window to the foreground. This means that the end user can effectively switch document windows during this operation using only the mouse. Of course, if the intended target window is not visible, none of this helps—the user has to rearrange windows before the operation.

IDropTarget::DragOver

DoDragDrop calls this function frequently: whenever the mouse moves, a key or button changes state, or a pulse timer elapses. Again, the "pulsing" is necessary to support scrolling in a target document, as we'll see later in Patron.

DragOver should be optimized to avoid costly operations, keeping the user interface crisp instead of sluggish. In general, you should perform hit-testing on the mouse location, determine a new effect flag, and update your user feedback similar to the way you did in DragEnter. OLE passes only three arguments to this function—grfKeyState, pt, and pdwEffect—with the same types and meanings as for DragEnter. (Again, pt is in screen coordinates.) If you need to access the source's data object, perhaps to check a format, you must save that pointer in DragEnter. If you only need to check a format again, you can just as well check it with DragEnter and save a flag, avoiding another call to IDataObject::QueryGetData. If you need a rendering of the data for any reason, ask for it in DragEnter; doing so in DragOver will perform a lot of unnecessary work.

In any case, there are three steps to perform in DragOver:

Check whether pt is an allowable drop point given the effect defined by grfKeyState and the availability of data from the source. If no drop is allowed, return DROPEFFECT_NONE and NOERROR.

If a drop is allowed, determine the effect as in DragEnter and store it in *pdwEffect. If you allow scrolling, check those conditions and add DROPEFFECT_SCROLL to the effects.

Update your user feedback (if any) appropriate for the effect and return NOERROR.

Cosmo's implementation is quite simple:


STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
, LPDWORD pdwEffect)
{
if (NULL==m_pIDataObject)
{
*pdwEffect=DROPEFFECT_NONE;
return NOERROR;
}

//We can always drop; return effect flags based on keys.
*pdwEffect=DROPEFFECT_MOVE;

if (grfKeyState & MK_CONTROL)
*pdwEffect=DROPEFFECT_COPY;

return NOERROR;
}

If Cosmo returned DROPEFFECT_NONE in DragEnter, the IDataObject pointer we might have saved is NULL. If that pointer is NULL, we know that no usable formats were in the data object, so we immediately return from DragOver with DROPEFFECT_NONE again. This is a fast means to check whether there is usable data. Otherwise, Cosmo simply updates the effect flags and returns. And because we don't care where the mouse is inside the document window, we leave the inverted border created in DragEnter where it stands, doing nothing else in DragOver.

IDropTarget::DragLeave

All good things come to an end; so too with drag and drop, in which a call to DragLeave tells a target the disappointing news that no drop will occur in it this time. The target must then clean up whatever state it has from DragEnter and DragOver. DragLeave itself takes no arguments, so you need to do only three things:

Remove any UI feedback in the target window.

Release any IDataObject held from DragEnter.

Return NOERROR.

Cosmo performs step 1 by calling CCosmoDoc::DropSelectTargetWindow again. This reverses the inversion done in DragEnter, leaving no visual feedback in the document. Cosmo then calls Release on the data object pointer and returns. The implementation of this is straightforward:


STDMETHODIMP CDropTarget::DragLeave(void)
{
if (NULL!=m_pIDataObject)
{
m_pDoc->DropSelectTargetWindow();
ReleaseInterface(m_pIDataObject));
}

return NOERROR;
}

IDropTarget::Drop

This must be the target's lucky day! It's been chosen to accept a drop when IDropTarget::Drop is called, and for the most part, all it needs to do is perform a paste and then clean up its state, as occurs in DragLeave. Drop is called with the same arguments as DragEnter. (Once again, pt is in screen coordinates.) The value of pIDataSource is the same as in DragEnter, but the values of the other parameters will likely change (unless the mouse did not move). Following are the steps that the target should perform in Drop:

Remove any end-user feedback in the target window and release any held pointer, as described for DragLeave. (You can simply call DragLeave if appropriate.)

Validate that a drop can occur at the location of the mouse (pt) and whether the source has the right data. If not, store DROPEFFECT_NONE in *pdwEffect but then return E_FAIL from the function.

Perform a Paste operation from the data object, returning DROPEFFECT_NONE and E_FAIL on error. Otherwise, store the effect flag according to grfKeyState and return NOERROR.

Cosmo's code is once again simple, exploiting the existing CCosmoDoc::PasteFromData to perform most of step 3:


STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
BOOL fRet=TRUE;

*pdwEffect=DROPEFFECT_NONE;

if (NULL==m_pIDataObject)
return ResultFromScode(E_FAIL);

DragLeave();

//No point in drag and drop to oneself (for Cosmo, at least)
if (m_pDoc->m_fDragSource)
return ResultFromScode(E_FAIL);

fRet=m_pDoc->PasteFromData(pIDataSource);

if (!fRet)
return ResultFromScode(E_FAIL);

*pdwEffect=DROPEFFECT_MOVE;

if (grfKeyState & MK_CONTROL)
*pdwEffect=DROPEFFECT_COPY;

return NOERROR;
}

Cosmo's implementation of Drop also has one other oddity—checking the document's m_fDragSource flag. If Cosmo detects that the end user dropped the data into the same window that it came from, there's nothing to do. We can simply return a failure code. For an application such as Patron, dragging data in the same document is useful for moving the data within a page—but Cosmo has no need for such a feature.