Sources and Targets: The OLE Drag and Drop Protocol

To illustrate how OLE Drag and Drop works, let's begin with an end user who wants to transfer selected data from a source to a consumer. The source and the consumer might be different applications, different windows (documents, say) in the same application, or the same window inside a single application. As shown in Figure 13-1, OLE sits between the two, so there really isn't any distinction. As with the clipboard, the source must wrap its selected data into a data object.

The problem for drag and drop is how to get the IDataObject pointer from the source to the consumer or target. To prepare for this, the source first requires an implementation of the IDropSource interface, as shown in Figure 13-2, in which the object in question needs only to support this one interface. IDropSource has two specific member functions, as shown in the table on the facing page.

Figure 13-1.

Prior to a drag-and-drop operation, the source has a data object for the selected data. The source and the consumer can be different applications, different documents in the same application, or the same document.

Function

Description

QueryContinueDrag

Determines what conditions continue or cancel an operation. Also determines what causes a drop because only the source knows what started the operation.

GiveFeedback

Sets the mouse cursor appropriate for an effect flag that indicates what would happen if the data were dropped in the target under the mouse cursor. The source can also control other user interface elements within this function.


Figure 13-2.

Objects necessary in both the source and the target to facilitate a drag-and-drop operation.

The target, in order to accept data from a drag-and-drop operation, must provide an object implementing IDropTarget, also shown in Figure 13-2. This object must then be attached to some window so that OLE can retrieve the IDropTarget pointer from the HWND it finds under the mouse cursor during a drag-and-drop operation. This association is accomplished through the OLE API function RegisterDragDrop, as shown in Figure 13-3. RegisterDragDrop takes an HWND and an IDropTarget pointer, calls IDropTarget::AddRef, and attaches that pointer as a window property to the HWND (using the Windows API SetWindowProp). At this point, the window is an open target. When the consumer no longer wants to be a target, it calls RevokeDragDrop, which removes the window property and releases the pointer.

Note: IDropTarget members are given the current mouse position in screen coordinates (in a POINTL structure). The target usually needs to call ScreenToClient before hit-testing the mouse position against other client-area coordinates.

Figure 13-3.

A consumer registers itself as a drop target by passing a window handle and an IDropTarget pointer to RegisterDragDrop.

IDropTarget itself has four specific member functions:

Function

Description

DragEnter

Indicates that the mouse has entered the window associated with this drop target. The target initializes its drag-and-drop state and provides visible feedback to the user.

DragOver

Indicates that the mouse has moved within the window, the keyboard state has changed, or an internal OLE timer has expired. This function provides the target with a pulse through which it controls user feedback and indicates the effect of a drop if it occurred at this very moment. The source is given this effect flag through IDropSource::GiveFeedback, in which it controls the mouse cursor.

DragLeave

Indicates that the mouse has moved out of the window. The target cleans up its state from DragEnter and DragOver and removes any visual feedback.

Drop

Indicates that the source's IDropSource::QueryContinueDrag said "drop." The target performs a paste and cleans up as in DragLeave.


The stage is now set for the operation to commence. The source must provide some means for starting a drag-and-drop operation, which is typically a WM_LBUTTONDOWN (or other mouse button message) on a particular region of the selected data, such as the outer edge of a rectangle. When this event occurs, the source passes its IDropSource and IDataObject pointers to the OLE function DoDragDrop, as shown in Figure 13-4.

Figure 13-4.

A source starts a drag-and-drop operation by passing its IDataObject and IDropSource to DoDragDrop.

Internally, DoDragDrop enters a loop that monitors changes in the state of the mouse and the keyboard, executing the following sequence, as illustrated in Figure 13-5:

Call WindowFromPoint (Win32) with the current mouse coordinates, and then attempt to retrieve the IDropTarget associated with the window if it exists. If not, DoDragDrop passes DROPEFFECT_NONE to the source's IDropSource::GiveFeedback, which displays a no-drop mouse cursor. If OLE doesn't find a drop target for a child window, it will attempt to find one for the parent window of the child, up to the top-level window of any such ancestry.

Call IDropTarget::DragEnter, passing the source's IDataObject (marshaled if necessary). The target determines whether there is usable data in the data object and returns an appropriate effect flag (DROPEFFECT_*) after initializing the data object's internal state.

DoDragDrop passes the effect flag to IDropSource::GiveFeedback. This changes the mouse cursor to indicate the effect and provides whatever feedback is appropriate in the source (such as indicating that a drop would delete the source data).

When the mouse moves within the target window, or when the Ctrl or Shift key is pressed, or when an internal pulse timer elapses, DoDragDrop calls IDropTarget::DragOver. IDropTarget::DragOver displays user feedback, scrolls the target window if necessary, and returns an effect flag (which OLE passes to the source). If the mouse moves out of the window, DoDragDrop calls IDropTarget::DragLeave for that target, possibly calling DragEnter for a new target (back to step 1).

If the Esc key is pressed or there is a change in a mouse button state, DoDragDrop calls IDropSource::QueryContinueDrag, which tells OLE to continue the operation, cancel it (if the Esc key is pressed), or perform a drop (if the correct mouse conditions are met to reverse the pick). A cancellation will result in a call to the current target's IDropTarget::DragLeave, whereas a drop calls IDropTarget::Drop.

The loop repeats until a drop or a cancellation occurs.

Figure 13-5.

The execution sequence of DoDragDrop.

Why does the source always maintain control of the mouse cursor? The reasoning is that when a drag-and-drop operation begins, the user is staring at the mouse cursor. From that point on, the cursor should consistently reflect any move, copy, or link effects regardless of the target under the mouse cursor. In other words, the three effects should have the same mouse cursor no matter what target specified that effect. The cursor should not change in different ways for different targets as a result of the same effect because the same cursor might then be used twice to indicate different effects. The only single agent in an entire operation is the source, so it retains control over this user interface element.