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)
|