Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Undocumented Corner


October 1996: Undocumented Corner

How MFC Does OLE Drag-and-Drop

George Shepherd and Scot Wingo

George develops and delivers courseware for developers using Win32, MFC, and OLE. When he's not delivering training courses, George develops software with Orbital Sciences Corp. Scot is a cofounder of Stingray Software. He can be contacted at [email protected]. As a senior trainer with DevelopMentor, They are the coauthors of MFC Internals (Addison-Wesley, 1996).


Back in the Windows 3.1 days, Microsoft added a drag-and-drop feature to the File Manager that allows users to drag-and-drop files. It was terrific at the time, greatly simplifying the File Manager. Still, all you could do with the File Manager version of drag-and-drop was move files around.

OLE drag-and-drop, on the other hand, is based on COM, and much more flexible than File-Manager drag-and-drop. OLE drag-and-drop lets you move all kinds of data around. In general, an OLE drag-and-drop transaction involves two COM objects-a data producer and consumer. For example, if you highlight some text in a word processor and drag it over to a spreadsheet, the word processor is the "data producer" and the spreadsheet the "data consumer." OLE drag-and-drop is accomplished using three interfaces: IDataObject, IDropSource, and IDropTarget. Data producers implement IDataObject and IDropSource, while data consumers implement IDropTarget.

OLE Drag-and-Drop Interfaces

At the heart of OLE drag-and-drop is an interface called IDataObject. Listing One presents its definition.

As with all COM interfaces, IDataObject is derived from IUnknown. In addition to the IUnknown functions, IDataObject has nine functions for performing data-transfer operations. Those necessary for OLE drag-and-drop are GetData(), QueryGetData(), and EnumFormatEtc(). GetData() retrieves data from the COM object behind the IDataObject interface. You can use QueryGetData() and EnumFormatEtc() to find out what kind of data is available for transfer.

As you peruse IDataObject's function list, you'll see some peculiar parameter names, including FORMATETC, which describes the data involved in the transaction, and STGMEDIUM, which represents the media used to transfer the data.

FORMATETC extends the normal clipboard format identifiers. In addition to describing the clipboard format of the data (CF_TEXT, CF_DIB, CF_METAFILEPIC, and the like), the structure can also supply information about how the data should be rendered. This includes information such as the intended target device, the aspect (bitmap versus thumbnail sketch render, for example), how much of the content to present, and the kind of media holding the data. Listing Two shows the FORMATETC structure. STGMEDIUM, on the other hand, is a tagged union that actually holds the data. Listing Three shows its structure. IDataObject uses FORMATETC to describe data and STGMEDIUM to represent data.

IDropSource handles the user interface for drag-and-drop operations. It's the data producer's job to implement this interface. Listing Four shows the actual interface definition.

The operating system uses QueryContinueDrag() to determine whether to continue or cancel a drag-and-drop operation. The operating system uses GiveFeedback() to tell the data producer to change the mouse cursor.

IDropTarget handles the target end of a drag-and-drop operation. IDropTarget is a callback interface. COM objects that wish to be drop targets implement IDropTarget. OLE uses this interface to notify the drop target whenever mouse-dragging action is occurring over the window associated with the drop target. Listing Five shows its interface.

Drag-and-Drop Status

During a drag-and-drop operation, the data source is constantly being asked to give some visual feedback; that is, to display the right kind of cursor. The data source knows what kind of cursor to display by looking at the DROPEFFECT values returned by the drop target. There are five different DROPEFFECT values that indicate the drag-and-drop transfer states; see Table 1.

Drag-and-Drop at the COM Level

During a computing session, an application tells Windows it would like to know when the user is trying to drag something over its window. At the raw COM level, the application uses the OLE API RegisterDragDrop() function to associate a window handle to the COM object implementing IDropTarget. Figure 1 illustrates OLE drag-and-drop at the COM level.

Later, a data producer calls DoDragDrop(), passing its IDataObject and IDropSource interfaces. Then DoDragDrop() enters the following loop.

    1.DoDragDrop() determines the window under the current cursor location. Remember, window handles are associated with IDropTarget interface pointers when data consumers call RegisterDragDrop(). If the window is a valid drop target, DoDragDrop() calls the IDropTarget::DragEnter() associated with that window handle. DoDragDrop() calls IDropSource::GiveFeedback() to tell the data producer what kind of cursor to display.

    2.From there, DoDragDrop() tracks mouse cursor movements and changes in the keyboard or mouse-button state. DoDragDrop() calls IDropTarget::DragLeave() when the cursor leaves the window. If the mouse enters another window, DoDragDrop determines if that window is a valid drop target and then calls the IDropTarget::DragEnter() associated with that window. As the cursor moves over a drop target window, DoDragDrop() calls IDropTarget::DragOver(). OLE also calls the data producer's IDropSource::GiveFeedback() here.

    3.If there is a change in the keyboard or mouse-button state, DoDragDrop() asks the data source what to do by calling IDropSource::QueryContinueDrag() and determines whether to continue the drag, drop the data, or cancel the operation.

    4.To complete the data drop, OLE calls IDropTarget::Drop(), passing the IDataObject interface to the data consumer so the consumer can fish the data out and use it.

Using MFC to Implement an OLE Drag-and-Drop Source

Data producers usually originate drag-and-drop transfers by responding to the WM_LBUTTONDOWN message. The mechanics for the data producer are straightforward: Instantiate a COleDataSource object and copy some data into it using CacheData() or CacheGlobalData(). Then call COleDataSource::DoDragDrop(), which is a wrapper around the OLE API function DoDragDrop(); see Listing Six.

Note that MFC has its own implementation of COleDropSource that is passed as a default parameter in COleDataSource::DoDragDrop().

Using MFC to Implement an OLE Drag-and-Drop Target

Implementing a drop target takes only a bit more work. Three classes are involved in creating a drop target: COleDataObject, COleDropTarget, and CView. (While you can use any CWnd-derived class for the target, you have to do a little more programming if you do.)

To enable a CView to be a drop target:

      1.Register a COleDropTarget with a CView.

      2.Override four virtual functions in the view-OnDragEnter(), OnDragOver(), OnDragLeave(), and OnDrop().

    To register a view as a drop target, declare a member variable of type COleDropTarget in your view class. Then handle the view's OnCreate() method (called right after a view is created). Inside OnCreate(), call COleDropTarget::Register(), sending it the view's pointer. Underneath the hood, COleDropTarget() calls the RegisterDragDrop() function, which registers a window handle as a drag-and-drop target within OLE.

    Once a window is registered with OLE to be a drag-and-drop target, it becomes eligible to receive drag-and-drop notifications. COleDropTarget is the actual receiver of the notifications, but then it forwards them to CView's OnDragEnter(), OnDragOver(), OnDragLeave(), and OnDrop() functions if the window happens to be a view.

    COleDataSource: How MFC Implements IDataObject

    MFC maintains two classes representing the IDataObject interface: COleDataSource, a COM object that implements the IDataObject interface; and COleDataObject, a C++ wrapper around an existing IDataObject pointer.

    COleDataSource is appropriately named-it's the starting point to a drag-and-drop transfer. Recall how OLE drag-and-drop works-an application places data into a COM object that implements IDataObject. It then hands the IDataObject pointer to Windows via a call to DoDragDrop(). When the destination application is notified of a drop, it uses the IDataObject pointer provided by the data source. In MFC, the COleDataSource stores the data in an array of AFX_DATACACHE_ENTRY structures. Listing Seven is the definition of the AFX_DATACACHE_ENTRY.

    There's nothing complicated here. The structure contains FORMATETC and STGMEDIUM members as well as a DATADIR member. You've already seen the FORMATETC and the STGMEDIUM structures. DATADIR is just an enumeration that establishes the direction of a data transfer (either getting or setting data). It's defined in the header file OBJIDL.H. The DATADIR field is used when the COleDataSource is in delayed-rendering mode.

    As just mentioned, COleDataSource maintains the array of the AFX_DATACACHE_ENTRY structures. The m_pDataCache member points to the first element in the array. CacheData() and CacheGlobalData() simply add elements to this array. COleDataSource consults this array whenever a data consumer tries to retrieve data from the IDataObject interface.

    Inside COleDataSource::DoDragDrop()

    On the surface, COleDataSource::DoDragDrop() appears to be just a wrapper for the API function ::DoDragDrop(). However, COleDataSource::DoDragDrop() does a bit more.

    When the data producer calls COleDataSource::DoDragDrop(), MFC examines the COleDropSource passed in by the caller. If the caller has provided a COleDropSource, DoDragDrop() uses that COleDropSource to manage the user interface (that is, changing the cursors and determining under which conditions the drag-and-drop operation can continue). If the caller doesn't provide a COleDropSource, DoDragDrop uses the default implementation of COleDropSource provided by MFC.

    If the caller provides a rectangle parameter, DoDragDrop() copies that rectangle into its own member rectangle (m_rectStartDrag). COleDataSource uses this rectangle to know when to start dragging. That is, the actual dragging won't commence until the cursor leaves this rectangle. If the data producer passes a NULL pointer, DoDragDrop() creates its own empty rectangle around the current position of the cursor so that drag-and-drop begins immediately.

    Before starting the drag operation, COleDataSource::DoDragDrop() calls COleDataSource::OnBeginDrag(). Since this function is virtual, you can override it. The default implementation of OnBeginDrag() first captures mouse input, then sets up a loop that acts as a timer.

    GetInterface() retrieves the IDataObject and IDropSource interfaces-the latter from the COleDropSource that was passed in. COleDataSource::DoDragDrop() uses these interface pointers to call the API function DoDragDrop().

    Once Windows has started a drag-and-drop transaction, it doesn't let go until a drop target accepts the data or the user cancels the operation.

    COleDataObject: How MFC Wraps IDataObject

    The concept behind COleDataObject is straightforward. As a wrapper around the IDataObject pointer, COleDataObject delegates most of its operations to it. For instance, COleDataObject::GetData(), COleDataObject::GetGlobalData(), and COleDataObject::GetFileData() all eventually find their way to IDataObject::GetData().

    Since COleDataObject is simply a wrapper around an existing IDataObject pointer, COleDataObject must supply some means of managing a COleDataObject-associated IDataObject interface. Those functions are COleDataObject::Attach() and COleDataObject::Detach().

    COleDataObject::Attach() and COleDataObject::Detach()

    COleDataObject keeps the IDataObject interface pointer in a data member called m_lpDataObject. Like other MFC classes that wrap native items (like handles), COleDataObject has a function for attaching an existing interface pointer to the class. That function is called Attach(). Attach takes two parameters: a pointer to an IDataObject interface and a flag indicating whether the interface pointer should be released when the COleDataObject object is destroyed. If m_lpDataObject is valid and if m_bAutoRelease is True, COleDataObject::Attach() releases m_lpDataObject. Then it sets the m_lpDataObject and the m_bAutoRelease members to the new values provided by parameters.

    There's also a complementary function called Detach() that sets m_lpDataObject to NULL. COleDataObject::Release() releases the IDataObject pointer if m_bAutoRelease is True.

    Once you've attached an IDataObject pointer to a COleDataObject, you can find out what's inside by querying for a specific format or by enumerating the COleDataObject's formats.

    Querying for Specific Formats

    COleDataObject provides the IsDataAvailable() function so the data consumer can find out what kind of data is in the COleDataObject. IsDataAvailable() takes a CLIPFORMAT and a FORMATETC as its parameters, and it calls the IDataObject interface's QueryGetData() to find out if the data is available in the given format.

    Enumerating Formats

    Recall that IDataObject has a function called EnumFormatEtc(), which returns a pointer to an interface that enumerates the clipboard formats. COleDataObject::BeginEnumFormats() starts the operation by retrieving the enumeration interface pointer from the underlying IDataObject interface. To retrieve the actual formats, clients call COleDataObject::GetNextFormat(). GetNextFormat() uses the enumerator passed back during the call to COleDataObject::BeginEnumFormats(), and then calls the enumerator's Next() function.

    When you determine that a COleDataObject has the kind of data you want, use COleDataObject::GetData() to retrieve it.

    COleDataObject::GetData():

    Retrieving the Data

    As a simple wrapper around IDataObject::GetData(), COleDataObject is straightforward. GetData() takes a clipboard format (like CF_TEXT), a FORMATETC structure, and a STGMEDIUM structure. If the data inside COleDataObject matches that criteria, then COleDataObject::GetData() returns the data in the STGMEDIUM structure.

    COleDropSource:

    How MFC Implements IDropSource

    IDropSource::QueryContinueDrag() and IDropSource::GiveFeedback() map directly to the virtual functions COleDropSource::QueryContinueDrag() and COleDropSource::GiveFeedback(). Since these functions are virtual, you can safely override them: for example, if you're unhappy with the default cursors provided by MFC.

    COleDropSource also has static members for specifying the time delay (m_nDragDelay) and the number of pixels the mouse must move before drag-and-drop starts. COleDropSource's constructor initializes these variables. The MFC documentation doesn't tell you this, but the drag distance and drag delay variables are read in from the WIN.INI file. The drag distance's key is DragMinDist and the drag delay's key is DragDelay. If these entries aren't in the initialization file, the drag distance's value is set to 2 and the drag delay's value is set to 200. (These are actually the DD_DEFDRAGMINDIST and DD_DEFDRAGDELAY constants in OLEIDL.H.)

    To change any of the user-interface characteristics for a given drag-and-drop operation, derive your own class from COleDropSource. Then feed your newly derived class into COleDataSource::DoDragDrop().

    COleDropTarget and CView:

    How MFC Implements IDropTarget

    COleDropTarget is a real COM object implemented using MFC's interface maps. COleDropTarget's most important members are Register(), DragEnter(), DragOver(), DragLeave(), and Drop().

    COleDropTarget::Register() is simply a wrapper around the OLE API function RegisterDragDrop(). Register() takes a single pointer to a CWnd-derived class and registers the window object's handle with Windows. COleDropTarget::Register() hands over its IDropTarget interface.

    The other important class in MFC's drag-and-drop implementation is CView. COleDropTarget usually works in concert with CView to make it very easy to program OLE drag-and-drop in a view. If you pass in a CView-derived object to COleDropTarget::Register(), COleDropTarget (which receives the raw drag-and-drop callbacks) forwards the notifications to the CView-derived class. That's why CView has the OnDragEnter(), OnDragOver(), OnDragLeave(), and OnDrop() functions.

    If you decide to use another CWnd-derived class as your drop target, then there's only a little more work involved. You need to derive your own class from COleDropTarget, then override the OnDragEnter(), OnDragOver(), OnDragLeave(), and OnDrop() functions to handle the data transfers that CView would have handled.

    Conclusion

    OLE drag-and-drop is implemented very well by MFC, providing an interactive, visual way to transfer data within an application or even between applications. Using MFC to implement drag-and-drop is straightforward if you follow the MFC cookbooks. However, understanding how drag-and-drop works at the COM level and how MFC implements drag-and-drop underscores the power of COM, illustrating the kinds of things you can do by programming in COM.

    Table 1: DROPEFFECT values.

    State               Description
    
    DROPEFFECT_NONE     Drop is not allowed.
    DROPEFFECT_COPY     Copy operation is pending.
    DROPEFFECT_MOVE     Move operation is pending.
    DROPEFFECT_LINK     Link from the dropped data to the original data is pending.
    DROPEFFECT_SCROLL   Drag scroll operation is about to occur or is occurring in the target.
    
    

    Figure 1: OLE drag-and-drop at the COM level.

    Listing One

    interface IDataObject : IUnknown {
      virtual   HRESULT  GetData(FORMATETC* pformatetc, STGMEDIUM* pmedium) = 0;
      virtual   HRESULT  GetDataHere(FORMATETC* pformatetc,STGMEDIUM* pmedium) = 0;
      virtual   HRESULT  QueryGetData(FORMATETC* pformatetc) = 0;
      virtual   HRESULT  GetCanonicalFormatEtc(FORMATETC* pformatetcIn, 
                                           FORMATETC* pformatetcOut) = 0;
      virtual   HRESULT  SetData(FORMATETC* pformatetc, 
                                           STGMEDIUM* pmedium, BOOL fRelease) = 0; 
      virtual   HRESULT  EnumFormatEtc(DWORD wDirection, 
                                           IEnumFORMATETC** penumformatetc) = 0;
      virtual   HRESULT  DAdvise(FORMATETC* pformatetc, 
                                           DWORD grfAdvf, IAdviseSink* pAdvSink, 
                                           DWORD* pdwConnection) = 0; 
      virtual   HRESULT  DUnadvise(DWORD dwConnection) = 0;
      virtual   HRESULT  EnumDAdvise(IEnumSTATDATA** ppenumAdvise) = 0;
    };
    

    Listing Two

    typedef struct tagFORMATETC {
       CLIPFORMAT        cfFormat;  
       DVTARGETDEVICE *  ptd;       
       DWORD             dwAspect;  
       LONG              lindex;    
       DWORD             tymed; // enumeration representing media
    } FORMATETC;

    Listing Three

    typedef struct tagSTGMEDIUM {
        DWORD tymed; // enumeration representing media
        union {
            HBITMAP hBitmap;
            HMETAFILEPICT hMetaFilePict;
            HENHMETAFILE hEnhMetaFile;
            HGLOBAL hGlobal;
            LPOLESTR lpszFileName;
            IStream *pstm;
            IStorage *pstg;
            } u;
        IUnknown *pUnkForRelease;
    }STGMEDIUM;

    Listing Four

    interface IDropSource : public IUnknown {
    public:
      virtual HRESULT QueryContinueDrag(BOOL fEscapePressed,DWORD grfKeyState) = 0;
      virtual HRESULT GiveFeedback(DWORD dwEffect) = 0;
    };

    Listing Five

    interface IDropTarget : public IUnknown {
    public:
       virtual HRESULT DragEnter(IDataObject *pDataObj, DWORD grfKeyState,
                                 POINTL pt, DWORD *pdwEffect) = 0;
       virtual HRESULT DragOver(DWORD grfKeyState, POINTL pt,DWORD *pdwEffect) = 0;
       virtual HRESULT DragLeave(void) = 0;
       virtual HRESULT Drop(IDataObject *pDataObj, DWORD grfKeyState,
                                POINTL pt, DWORD *pdwEffect) = 0;
    };

    Listing Six

    void OnLButttonDown() {
        COleDataSource cods;
        LPCSTR source;
        souce = "What's up Doc?";
        HGLOBAL h = GlobalAlloc(GHND | GMEM_SHARE, strlen(source) + 1);
        strcpy(LPSTR(GlobalLock(h)), source);
        GlobalUnlock(h);
        cods.CacheGlobalData(CF_TEXT, h);
    
        DROPEFFECT de = cods.DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE);
        if ( de == DROPEFFECT_MOVE) {
            // Do any special stuff depending on drop effect
        }
    }
    
    

    Listing Seven

    struct AFX_DATACACHE_ENTRY {
       FORMATETC m_formatEtc;
       STGMEDIUM m_stgMedium;
       DATADIR m_nDataDir;
    };
    
    
    


    Related Reading


    More Insights






    Currently we allow the following HTML tags in comments:

    Single tags

    These tags can be used alone and don't need an ending tag.

    <br> Defines a single line break

    <hr> Defines a horizontal line

    Matching tags

    These require an ending tag - e.g. <i>italic text</i>

    <a> Defines an anchor

    <b> Defines bold text

    <big> Defines big text

    <blockquote> Defines a long quotation

    <caption> Defines a table caption

    <cite> Defines a citation

    <code> Defines computer code text

    <em> Defines emphasized text

    <fieldset> Defines a border around elements in a form

    <h1> This is heading 1

    <h2> This is heading 2

    <h3> This is heading 3

    <h4> This is heading 4

    <h5> This is heading 5

    <h6> This is heading 6

    <i> Defines italic text

    <p> Defines a paragraph

    <pre> Defines preformatted text

    <q> Defines a short quotation

    <samp> Defines sample computer code text

    <small> Defines small text

    <span> Defines a section in a document

    <s> Defines strikethrough text

    <strike> Defines strikethrough text

    <strong> Defines strong text

    <sub> Defines subscripted text

    <sup> Defines superscripted text

    <u> Defines underlined text

    Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

     
    Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.