Using COM to Accept Interprocess Drag and Drop operations

By: mykle hoban

Abstract: This article describes how to implement an OLE Drop Target.

Accepting Drag and Drop Operations from Other Processes

Table Of Contents
Objective
Overview/Diagram
Explanation
Code
Download Sample Code

Objective

In this article, I will seek to explain how to make your application accept files dropped from other processes. For this document, I assume you have some knowledge of the following concepts:

  • C++Builder
  • COM/OLE
  • Win32 API

Overview and Diagrams

Drag and drop operations are one of the core features of Win32 "user-friendliness." Dragging and dropping objects, files, icons, and other things makes an application easy to use and visually pleasing. One particularly powerful feature of Win32 drag and drop is to be able to drag items between programs, i.e. dragging a picture out of IE onto your form.

Inter-process drag-and-drop is accomplished by implementing certain COM interfaces depending on what services your application requires. In order to for the user to be able to drag objects off your application, you must implement IDropSource. To accept files, implement IDropTarget. In this article, I will focus mainly on the use and implementation of IDropTarget, but in order to provide better understanding, I will explain in brief the operation of IDropSource.

Here is an overview diagram of what occurs during an inter-process drag and drop operation:

As you can see, the "Server" (the drag source) provides an IDropSource interface to the system. The system also gets the data from the server wrapped in an IDataObject interface. When the user drags the mouse over the drop target, the system calls methods on both target and source interfaces to execute the transfer of data.

For the purposes of this article, we really only need to concern ourselves with IDropTarget. Here are the methods of IDropTarget (in vtable order) which we will have to implement to allow drops in our application:

MethodDescription
QueryInterfaceReturn supported interfaces (in this case, IDropTarget and IUnknown).
AddRefIncrement reference count.
ReleaseDecrement reference count.

MethodDescription
DragEnterCalled when the drag operation enters the drop target area.
DragOverCalled when the drag operation moves over the target area.
DragLeaveCalled when the drag operation leaves the target area.
DropCalled when an object is dropped on the target area.

These are the methods we have to implement to make our application an available drop target, but we're not completely finished yet. There is one more thing we must do. We have to use the RegisterDragDrop and RevokeDragDrop functions to let the system know we're ready to accept drops. It remains up to our implementation to determine what kind of objects we allow.

Explanation

Let's jump into implementing our drop target. We will implement this as a component that can be dropped on a form and attached to any visual control. Here are our first two classes: TDropTargetImpl and TGenericVCLDropTarget. TDropTargetImpl is pretty simple, all it does is call back to the TGenericVCLDropTarget that owns it, so any drop target implementation can deal with it however it wants (i.e text droptarget, bitmap droptarget, etc.). TDropTargetImpl will implement QueryInterface, AddRef, and Release, but nothing else. TGenericDropTarget will be pretty much abstract as well. It will contain a property of type TWinControl that we'll use as our target to register with the system.

The class that does the real work for our example is TTextDropTarget. This class will derive from TGenericVCLDropTarget and will contain the necessary functions for getting the drop. Most of the code is pretty self-explanatory. I will focus on the two methods that are really important to us. The first is the DragEnter method. This is where we determine if we want to allow the drop or not.

HRESULT __stdcall TTextDropTarget::DragEnter(IDataObject *pDataObject,
                      DWORD grfKeyState, _POINTL pt, DWORD *pdwEffect)
{
  FORMATETC fe;
  setFE(fe,CF_TEXT,TYMED_HGLOBAL); // fill in structure for text
  HRESULT hres = pDataObject->QueryGetData(&fe); //do we like what this dataobject has?
  if (SUCCEEDED(hres) && FOnDragEnter)
  {
    /*the QueryGetData method returns an HRESULT
    depending on whether or not we could
    actually get the data if we tried. If we
    return a failing HRESULT here, the droptarget
    will not allow the drop*/
    //here's where we determine the keystates, etc. for the event
    TShiftState ss;
    ss.Clear();
    if (grfKeyState & MK_CONTROL)
      ss = ss << ssCtrl;
    if (grfKeyState & MK_ALT)
      ss = ss << ssAlt;
    if (grfKeyState & MK_SHIFT)
      ss = ss << ssShift;
    if (grfKeyState & MK_LBUTTON)
      ss = ss << ssLeft;
    if (grfKeyState & MK_MBUTTON)
      ss = ss << ssMiddle;
    if (grfKeyState & MK_RBUTTON)
      ss = ss << ssRight;
    FOnDragEnter(TargetControl,pt.x,pt.y,ss);
  }
  return hres;
}

The crucial line is the QueryGetData call. This basically makes or breaks the drag and drop operation. Our drop target will only allow the drop if DragEnter returns S_OK. Notice also the setFE call. This is just a macro I wrote that makes it easy to fill in FORMATETC structure. This is the same kind of thing as when dealing with clipboard data. For debugging purposes, or if you want to make a certain kind of drop target but don't know what type(s) to accept, here is a code snippet that shows you how to enumerate the incoming FORMATETC types. (This would be put under DragEnter).


IEnumFORMATETC *pefe;
FORMATETC fe;

pDataObject->EnumFormatEtc(DATADIR_GET,&pefe);
unsigned long fetch;
do {
  pefe->Next(1,&fe,&fetch);
  //do stuff with fe
} while (fetch);
pefe->Release();

Next is the Drop method. This is where we actually pull the data over and give it to our application. In this case, I just use an event called OnDrop that gets an AnsiString containing the text.


HRESULT __stdcall TTextDropTarget::Drop(IDataObject *pDataObject, DWORD grfKeyState,
                    _POINTL pt, DWORD *pdwEffect)
{
  //here's where we do all the stuff that's important.
  if (FOnDrop)
  {
    FORMATETC fe;
    STGMEDIUM stg;
    HRESULT hres;
    String dropstr;
    setFE(fe,CF_TEXT,TYMED_HGLOBAL);
    hres = pDataObject->GetData(&fe,&stg);
    //GetData returns us data in a storage medium structure.
    if (SUCCEEDED(hres))
    {
      dropstr = String((char *)GlobalLock(stg.hGlobal),GlobalSize(stg.hGlobal));
      GlobalUnlock(stg.hGlobal);
      ReleaseStgMedium(&stg);
      TShiftState ss;
      ss.Clear();
      if (grfKeyState & MK_CONTROL)
        ss = ss << ssCtrl;
      if (grfKeyState & MK_ALT)
        ss = ss << ssAlt;
      if (grfKeyState & MK_SHIFT)
        ss = ss << ssShift;
      if (grfKeyState & MK_LBUTTON)
        ss = ss << ssLeft;
      if (grfKeyState & MK_MBUTTON)
        ss = ss << ssMiddle;
      if (grfKeyState & MK_RBUTTON)
        ss = ss << ssRight;
      FOnDrop(TargetControl,pt.x,pt.y,ss,dropstr);
    }
    return hres;
  }
  else return S_OK;
}

This should be pretty straight-forward. We call GetData and fill a STGMEDIUM. We then fill the AnsiString from the global memory block contained in the STGMEDIUM, and release the STGMEDIUM.

This is really all there is to it. If you wanted to make a drop target that accepted images, you would really only have to adjust DragEnter and Drop to deal with the format type. Enjoy.

Code

Here is the complete code for the TTExtDropTarget component.

GenericVCLDropTarget.h

//---------------------------------------------------------------------------

#ifndef GenericVCLDropTargetH
#define GenericVCLDropTargetH
//---------------------------------------------------------------------------
#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include <oleidl.h>
//---------------------------------------------------------------------------
//this macro makes filling in a FORMATETC structure easy
#define setFE(fe, cf, med)  
   ((fe).cfFormat=cf, 
    (fe).dwAspect=DVASPECT_CONTENT, 
    (fe).ptd=NULL, 
    (fe).tymed=med, 
    (fe).lindex=-1)

class PACKAGE TGenericVCLDropTarget : public TComponent
{
  friend class TDropTargetImpl;
private:
  TDropTargetImpl *m_dt;
  TWinControl *m_DropTarget;
  bool registered;
protected:
  void __fastcall SetDropTarget(TWinControl *target);
  virtual HRESULT __stdcall DragEnter(IDataObject *pDataObject,
                      DWORD grfKeyState, _POINTL pt, DWORD *pdwEffect)=0;
  virtual HRESULT __stdcall DragOver(DWORD grfKeyState, _POINTL pt,
                      DWORD *pdwEffect)=0;
  virtual HRESULT __stdcall DragLeave()=0;
  virtual HRESULT __stdcall Drop(IDataObject *pDataObject, DWORD grfKeyState,
                      _POINTL pt, DWORD *pdwEffect)=0;
  __property TWinControl *TargetControl = {read=m_DropTarget, write=SetDropTarget};
  __property bool Registered = {read=registered};
public:
  __fastcall TGenericVCLDropTarget(TComponent* Owner);
  __fastcall ~TGenericVCLDropTarget();
__published:
};

class TDropTargetImpl : public IDropTarget
{
private:
  ULONG ref_count;
  TGenericVCLDropTarget *backptr;
public:
  TDropTargetImpl(TGenericVCLDropTarget *obj) { ref_count=0; backptr=obj; }
  HRESULT __stdcall QueryInterface(REFIID riid, void **ppv);
  ULONG __stdcall AddRef() { InterlockedIncrement((long *)&ref_count); return ref_count; }
  ULONG __stdcall Release() { if (InterlockedDecrement((long *)&ref_count) <= 0)
                                  delete this;
                              return ref_count; }
  HRESULT __stdcall DragEnter(IDataObject *pDataObject,
                      DWORD grfKeyState, _POINTL pt, DWORD *pdwEffect);
  HRESULT __stdcall DragOver(DWORD grfKeyState, _POINTL pt,
                      DWORD *pdwEffect);
  HRESULT __stdcall DragLeave();
  HRESULT __stdcall Drop(IDataObject *pDataObject, DWORD grfKeyState,
                      _POINTL pt, DWORD *pdwEffect);
};
//---------------------------------------------------------------------------
#endif

GenericVCLDropTarget.cpp

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "GenericVCLDropTarget.h"
#pragma package(smart_init)

//---------------------------------------------------------------------------
__fastcall TGenericVCLDropTarget::TGenericVCLDropTarget(TComponent* Owner)
  : TComponent(Owner)
{
  registered = false;
  m_DropTarget = NULL;
  OleInitialize(NULL);
  m_dt = new TDropTargetImpl(this);
}
//---------------------------------------------------------------------------
__fastcall TGenericVCLDropTarget::~TGenericVCLDropTarget()
{
  m_dt->Release();
  //delete m_dt;
  OleUninitialize();
}
//---------------------------------------------------------------------------
void __fastcall TGenericVCLDropTarget::SetDropTarget(TWinControl *target)
{
  if (!ComponentState.Contains(csDesigning))
  {
    if (registered && m_DropTarget)
      RevokeDragDrop(m_DropTarget->Handle);
    m_DropTarget = target;
    try {
      RegisterDragDrop(m_DropTarget->Handle,m_dt);
      registered = true;
    }
    catch (...)
    {
      registered = false;
    }
  }
  else
    m_DropTarget = target;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

HRESULT __stdcall TDropTargetImpl::QueryInterface(REFIID riid, void **ppv)
{
  if (IsEqualIID(riid,IID_IUnknown))
  {
    *ppv = static_cast<IUnknown *>(this);
    return S_OK;
  }
  else if (IsEqualIID(riid,IID_IDropTarget))
  {
    *ppv = static_cast<IDropTarget *>(this);
    return S_OK;
  }
  else
  {
    *ppv = NULL;
    return E_NOINTERFACE;
  }
}
HRESULT __stdcall TDropTargetImpl::DragEnter(IDataObject *pDataObject,
                      DWORD grfKeyState, _POINTL pt, DWORD *pdwEffect)
{
  return backptr->DragEnter(pDataObject,grfKeyState,pt,pdwEffect);
}
HRESULT __stdcall TDropTargetImpl::DragOver(DWORD grfKeyState, _POINTL pt,
                    DWORD *pdwEffect)
{
  return backptr->DragOver(grfKeyState,pt,pdwEffect);
}
HRESULT __stdcall TDropTargetImpl::DragLeave()
{
  return backptr->DragLeave();
}
HRESULT __stdcall TDropTargetImpl::Drop(IDataObject *pDataObject, DWORD grfKeyState,
                    _POINTL pt, DWORD *pdwEffect)
{
  return backptr->Drop(pDataObject,grfKeyState,pt,pdwEffect);
}

TextDropTarget.h

//---------------------------------------------------------------------------

#ifndef TextDropTargetH
#define TextDropTargetH
//---------------------------------------------------------------------------
#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include "GenericVCLDropTarget.h"
//---------------------------------------------------------------------------
typedef void __fastcall (__closure *TDragEvent)
      (TObject *Sender, int x, int y, TShiftState Shift );
typedef void __fastcall (__closure *TDropEvent)
      (TObject *Sender, int x, int y, TShiftState Shift, AnsiString DropText);

class PACKAGE TTextDropTarget : public TGenericVCLDropTarget
{
private:
  TDragEvent FOnDragEnter;
  TDragEvent FOnDragOver;
  TNotifyEvent FOnDragLeave;
  TDropEvent FOnDrop;
protected:
  HRESULT __stdcall DragEnter(IDataObject *pDataObject,
                      DWORD grfKeyState, _POINTL pt, DWORD *pdwEffect);
  HRESULT __stdcall DragOver(DWORD grfKeyState, _POINTL pt,
                      DWORD *pdwEffect);
  HRESULT __stdcall DragLeave();
  HRESULT __stdcall Drop(IDataObject *pDataObject, DWORD grfKeyState,
                      _POINTL pt, DWORD *pdwEffect);
public:
  __fastcall TTextDropTarget(TComponent* Owner);
__published:
  __property TDragEvent OnDragEnter = {read=FOnDragEnter, write=FOnDragEnter};
  __property TDragEvent OnDragOver = {read=FOnDragOver, write=FOnDragOver};
  __property TNotifyEvent OnDragLeave = {read=FOnDragLeave, write=FOnDragLeave};
  __property TDropEvent OnDrop = {read=FOnDrop, write=FOnDrop};
  __property TargetControl;
  __property Registered;
};
//---------------------------------------------------------------------------
#endif

TextDropTarget.cpp

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "TextDropTarget.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------
// ValidCtrCheck is used to assure that the components created do not have
// any pure virtual functions.
//

static inline void ValidCtrCheck(TTextDropTarget *)
{
  new TTextDropTarget(NULL);
}
//---------------------------------------------------------------------------
__fastcall TTextDropTarget::TTextDropTarget(TComponent* Owner)
  : TGenericVCLDropTarget(Owner)
{
}
//---------------------------------------------------------------------------
HRESULT __stdcall TTextDropTarget::DragEnter(IDataObject *pDataObject,
                      DWORD grfKeyState, _POINTL pt, DWORD *pdwEffect)
{
  FORMATETC fe;
  setFE(fe,CF_TEXT,TYMED_HGLOBAL); // fill in structure for text
  HRESULT hres = pDataObject->QueryGetData(&fe); //do we like what this dataobject has?
  if (SUCCEEDED(hres) && FOnDragEnter)
  {
    /*the QueryGetData method returns an HRESULT
    depending on whether or not we could
    actually get the data if we tried. If we
    return a failing HRESULT here, the droptarget
    will not allow the drop*/
    //here's where we determine the keystates, etc. for the event

    TShiftState ss;
    ss.Clear();
    if (grfKeyState & MK_CONTROL)
      ss = ss << ssCtrl;
    if (grfKeyState & MK_ALT)
      ss = ss << ssAlt;
    if (grfKeyState & MK_SHIFT)
      ss = ss << ssShift;
    if (grfKeyState & MK_LBUTTON)
      ss = ss << ssLeft;
    if (grfKeyState & MK_MBUTTON)
      ss = ss << ssMiddle;
    if (grfKeyState & MK_RBUTTON)
      ss = ss << ssRight;
    FOnDragEnter(TargetControl,pt.x,pt.y,ss);
  }
  return hres;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TTextDropTarget::DragOver(DWORD grfKeyState, _POINTL pt,
                    DWORD *pdwEffect)
{
  if (FOnDragOver)
  {
    TShiftState ss;
    ss.Clear();
    if (grfKeyState & MK_CONTROL)
      ss = ss << ssCtrl;
    if (grfKeyState & MK_ALT)
      ss = ss << ssAlt;
    if (grfKeyState & MK_SHIFT)
      ss = ss << ssShift;
    if (grfKeyState & MK_LBUTTON)
      ss = ss << ssLeft;
    if (grfKeyState & MK_MBUTTON)
      ss = ss << ssMiddle;
    if (grfKeyState & MK_RBUTTON)
      ss = ss << ssRight;
    FOnDragOver(TargetControl,pt.x,pt.y,ss);
  }
  return S_OK; ///we don't really do anything special;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TTextDropTarget:: DragLeave()
{
  if (FOnDragLeave)
    FOnDragLeave(TargetControl);
  return S_OK;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TTextDropTarget::Drop(IDataObject *pDataObject, DWORD grfKeyState,
                    _POINTL pt, DWORD *pdwEffect)
{
  //here's where we do all the stuff that's important.
  if (FOnDrop)
  {
    FORMATETC fe;
    STGMEDIUM stg;
    HRESULT hres;
    String dropstr;
    setFE(fe,CF_TEXT,TYMED_HGLOBAL);
    hres = pDataObject->GetData(&fe,&stg);
    //GetData returns us data in a storage medium structure.
    if (SUCCEEDED(hres))
    {
      dropstr = String((char *)GlobalLock(stg.hGlobal),GlobalSize(stg.hGlobal));
      GlobalUnlock(stg.hGlobal);
      ReleaseStgMedium(&stg);
      TShiftState ss;
      ss.Clear();
      if (grfKeyState & MK_CONTROL)
        ss = ss << ssCtrl;
      if (grfKeyState & MK_ALT)
        ss = ss << ssAlt;
      if (grfKeyState & MK_SHIFT)
        ss = ss << ssShift;
      if (grfKeyState & MK_LBUTTON)
        ss = ss << ssLeft;
      if (grfKeyState & MK_MBUTTON)
        ss = ss << ssMiddle;
      if (grfKeyState & MK_RBUTTON)
        ss = ss << ssRight;
      FOnDrop(TargetControl,pt.x,pt.y,ss,dropstr);
    }
    return hres;
  }
  else return S_OK;
}
//---------------------------------------------------------------------------
namespace Textdroptarget
{
  void __fastcall PACKAGE Register()
  {
     TComponentClass classes[1] = {__classid(TTextDropTarget)};
     RegisterComponents("Drop Targets", classes, 0);
  }
}
//---------------------------------------------------------------------------


Server Response from: ETNASC04