Coded Article:  Drag and drop files from Explorer
By: Damon Chandler

   The ease of use of an interface (GUI) is of utmost importance in today's applications. Indeed, Windows itself is based upon the idea that the user should be able to accomplish everyday chores without keyboard interaction.  I still remember the MS-DOS days when copying a group of files took at least a dozen keystrokes, not accounting for the backspaces due to typos.  Today, with the GUI of Windows, I can move a group of files without even touching the keyboard.  Forget typos, they are a thing of the past.

        This technology is called drag and drop, and it allows the user freedom from the keyboard.  Windows has native support for drag and drop, unlike some other operating systems.  Implementing this functionality for objects within your applications is relatively simple since TControl has built in support for drag and drop.  What about objects beyond your application?  You may be wondering how you would call BeginDrag() for files on the desktop, for instance.  Luckily, Windows performs most of the work for you.

       There are a couple ways to accomplish this: (1) Handling the WM_DROPFILES message and (2) Using the OLE interface.  The examples below demonstrate dragging a file from explorer to a TMemo control. The OLE interface can be extended to handle many more data types than just files.  For example, you can drag a section of text or even a bitmap into your application.



 

(1) Handling the WM_DROPFILES message:

If a window is registered as a drop site, it will receive the WM_DROPFILES message when the user is dragging a file within its bounds.  This message is sent to the control itself, not the Form or Parent of the control.  Therefore, to get notification of the WM_DROPFILES message, you'll have to subclass the Memo control, and look for the message in the subclass procedure.  In the message handler, call the DragQueryFile() function to extract the filename and number of files.  In this case, we'll accept only one file at a time...
 

//in header...

   Controls::TWndMethod OldMemoWP;
   void __fastcall NewMemoWP(TMessage &Msg);

 


 
 
 

//in source...
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    // turn on file d&d for the TreeView
    DragAcceptFiles(Memo1->Handle, true); 

    // subclass the Memo
    OldMemoWP = Memo1->WindowProc;
    Memo1->WindowProc = NewMemoWP;
}

void __fastcall TForm1::NewMemoWP(TMessage &Msg)
{
   if (Msg.Msg == WM_DROPFILES)
    {
       // find the number of files dropped
       int num_files = DragQueryFile((HDROP)Msg.WParam, 0xFFFFFFFF,
                                      (LPSTR)NULL, NULL);

       // only accept one file at a time
       if (num_files != 1)
       {
            ShowMessage("too many files!");
            Msg.Result = 0;
           return;
       }

       // find the length of the filename
       int NameLength = DragQueryFile((HDROP)Msg.WParam, 0,
                                       NULL, NULL) + 1;

       // get the filename
       char *FileName = new char[NameLength];
       DragQueryFile((HDROP)Msg.WParam, 0, FileName, NameLength);

       // load the file
       Memo1->Lines->LoadFromFile(FileName);

       delete [] FileName;

       DragFinish((HDROP)Msg.WParam);
       Msg.Result = 0;
       return;
    }

    OldMemoWP(Msg);
}

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
    // restore the original window procedure
    Memo1->WindowProc = OldMemoWP;
}
 


 
 
 

(2) Implementing an OLE Drop Target

There is also the slightly more involved OLE method.  The first task is to implement a simple class derived from IDropTarget.  This class will handle most of the work, and is fairly expandable.  Remeber, you'll only have to do this once.  It then becomes reuseable in all applications.  You could even wrap this into a simple non-visual component if deisred.  Here's the header -- an explanation follows...
 

//in header...
//---------------------------------------------------------------------------
#ifndef DropTargetH
#define DropTargetH

#include <ole2.h>
#include <shlobj.h>

#define WM_OLEDROP WM_USER + 1
//---------------------------------------------------------------------------

class TDropTarget : public IDropTarget
{
private:
   unsigned long FReferences;
   bool FAcceptFormat;
   HWND FFormHandle;

    // helper function
   void __fastcall HandleDrop(HDROP HDrop);

protected:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    // IDropTarget methods
    STDMETHOD(DragEnter)(LPDATAOBJECT pDataObj, DWORD grfKeyState,
                                         POINTL pt, LPDWORD pdwEffect);
    STDMETHOD(DragOver)(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect);
    STDMETHOD(DragLeave)();
    STDMETHOD(Drop)(LPDATAOBJECT pDataObj, DWORD grfKeyState,
                    POINTL pt, LPDWORD pdwEffect);

public:
    __fastcall TDropTarget(HWND HForm);
    __fastcall ~TDropTarget();

};
//---------------------------------------------------------------------------
#endif

 


 
 

    There are two parts IUnknown and IDropTarget methods.  OLE is based on the component object model (COM) which use the IUnknown interface to respond to queries and maintain a reference count. Since all OLE interfaces are derived
from IUnknown, they are also COM objects. The IUnknown interface consists of three member functions which you must implement: QueryInterface, AddRef, and Release. These three methods are generic to most applications, ant can simply be cut and pasted. 

    The IDropTarget methods are necessary to handle the different types of data and how they are manipulated.  If you want to drop objects other than files, such as graphics, or text, you would simply change the implementation.  OLE uses the IDropTarget pointer to provide feedback of the state of a drop operation.  These functions are similar to the VCL's OnDrag* events.  To keep this class as reuseable as possible, I included the WM_OLEDROP message definition.  You'll notice that constructor to the class accepts a HWND parameter.  We'll use this handle to send the WM_OLEDROP mesage to the Form, which can then decode it.  This deosn't mean that the Form has to be registered as the drop site -- OLE is very flexible,  as you'll see...
 
 

//in source...
#include "DropTarget.h"
//---------------------------------------------------------------------------
__fastcall TDropTarget::TDropTarget(HWND HForm)
        : IDropTarget()
{
    FFormHandle = HForm;  // handle to your Form passed in the ctor
    FReferences = 1;
    FAcceptFormat = false;
}
//---------------------------------------------------------------------------

__fastcall TDropTarget::~TDropTarget()
{
}
//---------------------------------------------------------------------------

// helper routine to notify Form of drop on target
void __fastcall TDropTarget::HandleDrop(HDROP HDrop)
{
    SendMessage(FFormHandle, WM_OLEDROP, (WPARAM)HDrop, 0);
}
//---------------------------------------------------------------------------

// IUnknown Interface has three member functions:
// QueryInterface, AddRef, and Release.

STDMETHODIMP TDropTarget::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
   // tell other objects about our capabilities
   if (iid == IID_IUnknown || iid == IID_IDropTarget)
   {
       *ppv = this;
       AddRef();
       return NOERROR;
   }
   *ppv = NULL;
   return ResultFromScode(E_NOINTERFACE);
}
//---------------------------------------------------------------------------

STDMETHODIMP_(ULONG) TDropTarget::AddRef()
{
   return ++FReferences;
}
//---------------------------------------------------------------------------

STDMETHODIMP_(ULONG) TDropTarget::Release()
{
   if (--FReferences == 0)
   {
       delete this;
       return 0;
   }
   return FReferences;
}
//---------------------------------------------------------------------------

// IDropTarget Interface handles the Drag and Drop
// implementation

// Drag Enter is called first
STDMETHODIMP TDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState,
    POINTL pt, LPDWORD pdwEffect)
{
    FORMATETC fmtetc;

    fmtetc.cfFormat = CF_HDROP;
    fmtetc.ptd      = NULL;
    fmtetc.dwAspect = DVASPECT_CONTENT;
    fmtetc.lindex   = -1;
    fmtetc.tymed    = TYMED_HGLOBAL;

   // does the drag source provide CF_HDROP,
    // which is the only format we accept
   if (pDataObj->QueryGetData(&fmtetc) == NOERROR)
        FAcceptFormat = true;
   else FAcceptFormat = false;

   return NOERROR;
}
//---------------------------------------------------------------------------

// implement visual feedback if required
STDMETHODIMP TDropTarget::DragOver(DWORD grfKeyState, POINTL pt, 
    LPDWORD pdwEffect)
{
   return NOERROR;
}
//---------------------------------------------------------------------------

// remove visual feedback
STDMETHODIMP TDropTarget::DragLeave()
{
    FAcceptFormat = false;
   return NOERROR;
}
//---------------------------------------------------------------------------

// source has sent the DRAGDROP_DROP message indicating
// a drop has a occurred
STDMETHODIMP TDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState,
    POINTL pt, LPDWORD pdwEffect)
{
    FORMATETC fmtetc;
    fmtetc.cfFormat = CF_HDROP;
    fmtetc.ptd = NULL;
    fmtetc.dwAspect = DVASPECT_CONTENT;
    fmtetc.lindex = -1;
    fmtetc.tymed = TYMED_HGLOBAL;

   // user has dropped on us -- get the CF_HDROP data from drag source
    STGMEDIUM medium;
    HRESULT hr = pDataObj->GetData(&fmtetc, &medium);

   if (!FAILED(hr))
    {
       // grab a pointer to the data
        HGLOBAL HFiles = medium.hGlobal;
        HDROP HDrop = (HDROP)GlobalLock(HFiles);

       // call the helper routine which will notify the Form
        // of the drop
        HandleDrop(HDrop);

       // release the pointer to the memory
        GlobalUnlock(HFiles);
        ReleaseStgMedium(&medium);
    }
   else
    {
        *pdwEffect = DROPEFFECT_NONE;
       return hr;
    }
   return NOERROR;
}
//---------------------------------------------------------------------------
 
 

 


 
 
 

Now in your Form's code, initialize the OLE library, and register the Memo as a "drop target."  Also, since the TDropTarget class uses the SendMessage() function with the user-defined WM_OLEDROP message, you'll need to map the
message and provide a handler...
 


//in Form header...
    LPDROPTARGET lpDropTarget;
   void __fastcall WMOleDrop(TMessage &Msg);

BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(WM_OLEDROP, TMessage, WMOleDrop)
END_MESSAGE_MAP(TForm)

 


 
 

//in Form source...
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
   // initialize the OLE library
    OleInitialize(NULL);

   // pass the handle to the Form in the constructor
    // so TDropTarget knows who to send the WM_OLEDROP 
    // message to
    lpDropTarget = (LPDROPTARGET)new TDropTarget(Handle);
    CoLockObjectExternal(lpDropTarget, true, true);

   // register the Memo as a drop target
    RegisterDragDrop(Memo1->Handle, lpDropTarget);
}
 

void __fastcall TForm1::WMOleDrop(TMessage &Msg)
{
   // find the number of files dropped
   int num_files = DragQueryFile((HDROP)Msg.WParam, 0xFFFFFFFF,
                                  (LPSTR)NULL, NULL);

   if (num_files != 1)
    {
        ShowMessage("too many files!");
        Msg.Result = 0;
       return;
    }

   // find the length of the filename
    int NameLength = DragQueryFile((HDROP)Msg.WParam, 0,
                                   NULL, NULL) + 1;

   // get the filename
    char *FileName = new char[NameLength];
    DragQueryFile((HDROP)Msg.WParam, 0, FileName, NameLength);

   // load the file
    Memo1->Lines->LoadFromFile(FileName);

   delete [] FileName;

    DragFinish((HDROP)Msg.WParam);
    Msg.Result = 0;
}
 

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
   // remove the Memo from the list of drop targets
    RevokeDragDrop(Memo1->Handle);
    lpDropTarget->Release();
    CoLockObjectExternal(lpDropTarget, false, true);

   // uninitialize the OLE library
    OleUninitialize();
}

 


 
 

The example above is a poor demonstration of OLE's capabilities.  To find out how to add additional functionality to the TDropTarget class, have a look at these links...
 

http://msdn.microsoft.com/library/sdkdoc/shellcc/shell/DragDrop.htm

http://msdn.microsoft.com/library/devprods/vs6/visualc/vccore/_core_drag_and_drop_.28.ole.29.htm

http://support.microsoft.com/support/kb/articles/Q122/9/54.asp (sample code)