You are on page 1of 28

How to catch files dragged and dropped on

an application from Explorer

Contents
 Why do it?
 How it's done
o Overview
o Getting information about dropped files
o Putting it all together
o The Delphi way
o Going further
 Demo application

Why do it?
Many programs allow the user to open files by dragging them from Explorer and dropping
them on the program's window. This is often more convenient to the user than using the
normal File | Open menu option or toolbar button since it misses out the step of navigating an
Open File dialog box. Adding similar support to your program makes it appear more
professional.

How it's done


OLE Drag & Drop
OLE drag and drop is explored in the article "How to receive data dragged from other
applications".

There are two main ways to add support to a Windows application. The first is to use
Windows drag and drop API functions and handle a related Windows message. The second
method uses OLE (COM), and additionally supports inter and intra-application drag and
drop. We'll explore the first, and most simple, of these methods here.

Overview

File drag and drop is not enabled in Windows applications by default. To support it we need
to tell Windows we want to be notified about file drops, and nominate a window to receive
the notifications. The nominated window has to handle the WM_DROPFILES message that
Windows sends us when a drop occurs. The message supplies us with a "drop handle" that we
pass to various API routines to get information about what was dropped. When we've
finished good manners require that we tell Windows we no longer need to be told about
drops.

Windows Vista Security Problems


For security reasons Windows Vista may disallow drag and drop between windows that have
different security permissions. As a consequence WM_DROPFILES messages may be
blocked.
You can read more about the problem on this Microsoft forum

Here is a step by step guide to what we need to do in a Delphi application to handle dropped
files:

1. Use the ShellAPI unit to get access to the required API routines:

uses ShellAPI

Listing 1

2. Call DragAcceptFiles, passing the handle of the window that is to receive


WM_DROPFILES messages, along with a flag to indicate we want to receive
notifications. For example for our main program form to receive the messages:

DragAcceptFiles(Form1.Handle, True);

Listing 2

3. Handle the WM_DROPFILES message. In Delphi we can declare a message handler


for this event in the our main form's class declaration:

procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;

Listing 3

4. In the WM_DROPFILES message handler use the DragQueryXXX API functions to


retrieve information about the file drop – see the explanation below.
5. Notify Windows we have finished with the supplied drop handle. We do this by
calling DragFinish, passing it the drop handle we retrieved from the
WM_DROPFILES message. This frees the resources used to store information about
the drop:

DragFinish(DropH);

Listing 4

6. When we are closing down our application we call DragAcceptFiles again, but this
time with a final parameter of False to let Windows know we're no longer interested
in handling file drops:

DragAcceptFiles(Form1.Handle, False);

Listing 5

Getting information about dropped files


We use two API function to get information about dropped files – DragQueryFile and
DragQueryPoint. Both of these require the drop handle from the WM_DROPFILE messages.

DragQueryFile – jack of all trades

Like many Windows API functions, DragQueryFile can perform several functions depending
on the parameters passed to it. This makes it hard to remember exactly how to use it. Its
parameters (in Delphi-speak) are:

 DropHandle: HDROP – the drop handle provided by the WM_DROPFILES message.


 Index: Integer – the index of the file to query in the list of dropped files.
 FileName: PChar – pointer to a dropped file name.
 BufSize: Integer – size of buffer for the above.

The three functions the function performs are:

1. Finding the number of files dropped.


We get this information by passing $FFFFFFFF as the Index parameter, nil as the file
name parameter and 0 as the BufSize parameter. The return value is the number of
files dropped. Obvious isn't it!
2. Finding the size of buffer needed to store the file name at a specific (zero based)
index.
We pass the file's index number in the Index parameter, nil for the file name and 0 for
the buffer size. The function then returns the buffer size required.
3. Fetching the name of the file at a given index.
We pass the index number, a PChar buffer large enough to hold the file name + the
#0 terminator, and the actual buffer size.

All this makes for extremely unreadable code which, later in the article, we'll hide away in a
class.

DragQueryPoint

This function is quite an anti-climax after DragQueryFile – it simply does what it says and
provides the mouse cursor position where the files were dropped. The parameters are:

 DropHandle: HDROP – the drop handle from WM_DROPFILES.


 var Point: TPoint – reference to a TPoint structure that receives the required point.
The function returns non-zero if the drop was in the window's client area and zero if it
wasn't.

Putting it all together

Let's pull all we've discovered together in a skeleton Delphi application whose main form,
Form1, needs to be able to catch and process dropped files.

In our form creation event handler we register our interest with Windows:

procedure TForm1.FormCreate(Sender: TObject);


begin
// ... other code here
DragAcceptFiles(Self.Handle, True);
// ... other code here
end;
Listing 6

The meat of the processing comes in the WM_DROPFILES event handler:

procedure TForm1.WMDropFiles(var Msg: TWMDropFiles);


var
DropH: HDROP; // drop handle
DroppedFileCount: Integer; // number of files dropped
FileNameLength: Integer; // length of a dropped file name
FileName: string; // a dropped file name
I: Integer; // loops thru all dropped files
DropPoint: TPoint; // point where files dropped
begin
inherited;
// Store drop handle from the message
DropH := Msg.Drop;
try
// Get count of files dropped
DroppedFileCount := DragQueryFile(DropH, $FFFFFFFF, nil, 0);
// Get name of each file dropped and process it
for I := 0 to Pred(DroppedFileCount) do
begin
// get length of file name
FileNameLength := DragQueryFile(DropH, I, nil, 0);
// create string large enough to store file
// (Delphi allows for #0 terminating character automatically)
SetLength(FileName, FileNameLength);
// get the file name
DragQueryFile(DropH, I, PChar(FileName), FileNameLength + 1);
// process file name (application specific)
// ... processing code here
end;
// Optional: Get point at which files were dropped
DragQueryPoint(DropH, DropPoint);
// ... do something with drop point here
finally
// Tidy up - release the drop handle
// don't use DropH again after this
DragFinish(DropH);
end;
// Note we handled message
Msg.Result := 0;
end;
Listing 7
Using SetLength
SetLength(Str, N) creates a buffer large enough for N characters plus the terminating #0.

First we record the drop handle in a local variable for convenience and then make the first of
those confusing calls to DragQueryFile to get the number of files dropped. Now we loop
through each file by index (zero based, remember) and get the file names. For each file name
we first get the required buffer size using the second form of DragQueryFile, then create a
sufficiently large string. We finally read the string into the buffer using the third form of
DragQueryFile.
After reading all the file names we then get the drop point. Once all the processing is done we
call DragFinish to release the drop handle. Notice we do this in a finally section since the
drop handle is a resource that we need to ensure is freed, even if there is an exception. We
end by settting the message result to 0 to indicate to Windows that we have handled it.

Finally, we unregister our interest in file drops in the form destruction event handler:

procedure TForm1.FormDestroy(Sender: TObject);


begin
// ... other code here
DragAcceptFiles(Self.Handle, False);
// ... other code here
end;
Listing 8

The Delphi Way

If you agree with me that all the messing about with API routines rather spoils our Delphi
code, what better than to make a helper class to hide a lot of the mess. Here's a small class
that can be created and destroyed within the WM_DROPFILES message handler. The class
hides away a lot of the code, although we must still handle WM_DROPFILES ourselves. We
must also notify Windows of our intention to accept drag drop.

The class's declaration appears in Listing 9 while Listing 10 has the implementation.

type
TFileCatcher = class(TObject)
private
fDropHandle: HDROP;
function GetFile(Idx: Integer): string;
function GetFileCount: Integer;
function GetPoint: TPoint;
public
constructor Create(DropHandle: HDROP);
destructor Destroy; override;
property FileCount: Integer read GetFileCount;
property Files[Idx: Integer]: string read GetFile;
property DropPoint: TPoint read GetPoint;
end;
Listing 9
constructor TFileCatcher.Create(DropHandle: HDROP);
begin
inherited Create;
fDropHandle := DropHandle;
end;

destructor TFileCatcher.Destroy;
begin
DragFinish(fDropHandle);
inherited;
end;

function TFileCatcher.GetFile(Idx: Integer): string;


var
FileNameLength: Integer;
begin
FileNameLength := DragQueryFile(fDropHandle, Idx, nil, 0);
SetLength(Result, FileNameLength);
DragQueryFile(fDropHandle, Idx, PChar(Result), FileNameLength + 1);
end;

function TFileCatcher.GetFileCount: Integer;


begin
Result := DragQueryFile(fDropHandle, $FFFFFFFF, nil, 0);
end;

function TFileCatcher.GetPoint: TPoint;


begin
DragQueryPoint(fDropHandle, Result);
end;
Listing 10

There is not much to explain, given the code we have already seen. The constructor takes a
drop handle and records it. The drop handle is "freed" in the destructor with a call to
DragFinish. The list of dropped files, the number of files dropped and the drop point are all
provided as properties. The assessor methods for the properties are simply wrappers round
the DragQueryFile and DragQueryPoint API functions.

Let us rewrite the WM_DROPFILES message handler to use the new class:

procedure TForm1.WMDropFiles(var Msg: TWMDropFiles);


var
I: Integer; // loops thru all dropped files
DropPoint: TPoint; // point where files dropped
Catcher: TFileCatcher; // file catcher class
begin
inherited;
Catcher := TFileCatcher.Create(Msg.Drop);
try
for I := 0 to Pred(Catcher.FileCount) do
begin
// ... code to process file here
end;
DropPoint := Catcher.DropPoint;
// ... do something with drop point
finally
Catcher.Free;
end;
Msg.Result := 0;
end;
Listing 11

Going further

The TFileCatcher class could be extended to encapsulate all of the drop files functionality,
hiding away all the API calls. To do this would require access to the form's window so we
could intercept its messages and respond to WM_DROPFILES. One way to do this is to
subclass the form's window. It is beyond the scope of this article to look at how that can be
done. However, if you want to invetigate further, please check out my Drop Files
components.
How to receive data dragged from other
applications (part 1 of 6)
Contents and introduction

Contents
 Introduction
 About IDropTarget
 About IDataObject
 Understanding Data Formats
 Querying the Data Object
o Checking for a specified data format
o Enumerating all data formats
 Getting Data from the Data Object
o Example 1: Text stored in global memory
o Example 2: File list stored in global memory
o Example 3: Data stored in a stream
 Implementing IDropTarget
o Boilerplate code
o Example 1: Listing data formats
o Example 2: Displaying data from selected data formats
 Demo code
 Going Further
 Summary
 References
 Feedback
Introduction
In article #11 we looked at how to catch files dragged and dropped on our application from
Explorer. That's fine as far as it goes, but what about catching objects dragged and dropped
from other applications. For example, how do we catch a text selection dragged from a text
editor or word processor, or what about an HTML selection dragged from your web browser?

In order to receive such objects we have to make our application into a "drop target" that is
recognised by Windows. We do this using COM (or OLE) drag and drop. As is usual with
COM, we have to create an object that implements an interface – IDropTarget in this case –
and then tell Windows about it. Windows then calls the methods of our implementation of
IDropTarget to notify us of drag drop events.

If we accept a dropped object Windows makes the dropped object's data available via an
object that supports the IDataObject interface. This object can provide the data in one or
more data formats. Several different delivery mechanisms may be supported and objects can
be targetted at different output devices. Source applications may also advise how the data
should be rendered. Our application needs to examine the available data formats and decide if
it can accept any of them. If so we must also be able to extract the data from the data object.

In this article we will:

 Show how to register an application's window as a drop target.


 Interact with Windows to give visual feedback about whether we will accept a drop.
 Show how to examine all data formats supported by an object.
 Give examples of how to extract data from certain kinds of data object.

The first thing we need to do is to get to know the IDropTarget and IDataObject interfaces.
We do this in the next section.
How to receive data dragged from other
applications (part 2 of 6)
The IDropTarget and IDataObject interfaces

About IDropTarget
IDropTarget descends directly from IUnknown and implements four additional methods. The
definition of the interface is found in Delphi's ActiveX unit and is reproduced in Listing 1.

type
IDropTarget = interface(IUnknown)
['{00000122-0000-0000-C000-000000000046}']
function DragEnter(const dataObj: IDataObject; grfKeyState: Longint;
pt: TPoint; var dwEffect: Longint): HResult;
stdcall;
function DragOver(grfKeyState: Longint; pt: TPoint;
var dwEffect: Longint): HResult;
stdcall;
function DragLeave: HResult;
stdcall;
function Drop(const dataObj: IDataObject; grfKeyState: Longint;
pt: TPoint; var dwEffect: Longint): HResult;
stdcall;
end;
Listing 1

Table 1 provides a description of the methods and how we use them. Further information can
be found in Delphi's Windows API help file.

IDropTarget methods
Called when the user first drags an object over the window that is registered for
drag and drop. Parameters are
dataObj
Describes the object being dragged. We will examine this object in detail
later.
grfKeyState
Bit mask describing which mouse buttons and "shift" (modifier) keys are
pressed. We may want to use this information to decide how to handle
the operation and to help decide the value assigned to dwEffect (see
DragEnter
below).
pt
The mouse location.
dwEffect
Describes the cursor effect used for the operation. When the method is
called it contains a bit mask detailing the permitted effects and must be
set to a single value decribing the desired effect within the method.
Effects are: the "no entry" cursor (meaning we can't accept a drop), the
copy cursor, the move cursor or the shortcut cursor.
Called repeatedly as the user moves the mouse over our window. The parameters
are the same as for DragEnter except that dataObj is not provided here. We use
DragOver
this method to modify the the cursor according to the mouse position and any
change in the mouse button or modifier keys.
Called to inform us that the user has dragged the object off our window without
DragLeave
dropping the object. We use this to tidy up if necessary.
Called if the user drops the object over our window. The parameters are exactly
the same as for DragEnter. When this method is called we process the dropped
Drop
object and tidy up. Note that DragLeave is not called if the object is dropped.
Drop is not called if the "no entry" cursor has been requested.
Table 1

When implementing IDropTarget we may need to examine the data object, the permitted
drop effects and the modifier keys to answer the following questions:

 Can we accept the object?


 How should we handle the object if dropped?

We will answer these questions later in the article when we look at implementing
IDropTarget.

About IDataObject
Again the declaration of IDataObject is provided in the ActiveX unit. Listing 2 replicates the
definition.

type
IDataObject = interface(IUnknown)
['{0000010E-0000-0000-C000-000000000046}']
function GetData(const formatetcIn: TFormatEtc;
out medium: TStgMedium): HResult;
stdcall;
function GetDataHere(const formatetc: TFormatEtc;
out medium: TStgMedium): HResult;
stdcall;
function QueryGetData(const formatetc: TFormatEtc): HResult;
stdcall;
function GetCanonicalFormatEtc(const formatetc: TFormatEtc;
out formatetcOut: TFormatEtc): HResult;
stdcall;
function SetData(const formatetc: TFormatEtc;
var medium: TStgMedium; fRelease: BOOL): HResult;
stdcall;
function EnumFormatEtc(dwDirection: Longint; out enumFormatEtc:
IEnumFormatEtc): HResult;
stdcall;
function DAdvise(const formatetc: TFormatEtc;
advf: Longint; const advSink: IAdviseSink;
out dwConnection: Longint): HResult;
stdcall;
function DUnadvise(dwConnection: Longint): HResult;
stdcall;
function EnumDAdvise(out enumAdvise: IEnumStatData): HResult;
stdcall;
end;
Listing 2

IDataObject is supported by data objects dragged and dropped over our window. We don't
need to implement this interface – Windows provides an instance to us via the methods of
IDropTarget. Not all the methods are required when handling drag and drop. The only ones
of interest to us in this article are:

Required IDropTarget Methods


Unsurprisingly this method retrieves data from the drop object. We specify
a desired data format, medium and target device in the method's
GetData formatetcIn parameter and, if the object supports it, the method returns the
required data encapsulated in a storage medium via its medium parameter.
We will discuss storage media in detail later.
QueryGetData Checks whether the data object can provide data in the required format.
Returns an enumerator that can iterate through the various data formats,
EnumFormatEtc
media and target devices supported by the data object.
Table 2

The GetDataHere and GetCanonicalFormatEtc methods could also be of use but are not
considered in this article.

As has been observed, data objects can store different versions of the same data in various
formats. It is important that we have a reasonable understanding of them. In the next section
we examine data formats in more detail.
How to receive data dragged from other
applications (part 3 of 6)
Understanding data formats

Understanding Data Formats


In this section we will investigate various data formats used to transfer data from IDataObject
instances. Data formats can vary in four different ways:

1. The data format


Just like the clipboard a data object can store data in numerous different formats. In
fact, data objects use the same way of describing data formats as that used by the
clipboard. A format may be one of the built-in ones such as CF_TEXT or an
application defined format such as "Rich Text Format".
2. The storage medium used to transfer the data
The data can be delivered via various different mechanisms. These mechanisms are
described by the TYMED_* enumeration and are:

TYMED_HGLOBAL
The data is transferred via global memory accessed via a global memory handle of
type HGLOBAL.
TYMED_FILE
The data is transferred via a disk file.
TYMED_ISTREAM
The data is transferred as a stream accessed through an IStream interface.
TYMED_ISTORAGE
The data is transferred in a Windows storage object accessed through an IStorage
interface.
TYMED_GDI
The data is specified by a GDI component accessed via a HBITMAP handle.
TYMED_MFPICT
The data is a metafile accessed via a HMETAFILEPICT handle.
TYMED_ENHMF
The data is an enhanced metafile accessed via a HENHMETAFILE handle.

The way we access the data depends on the storage medium. By far the most common
storage medium is the global memory handle and we will concentrate mainly on that
mechanism in this article.

3. The "Aspect", or view of the data


This indicates how much detail should be provided when rendering the data. The
possible aspects are: a full representation as an embedded object, a thumbnail view,
an icon view or a print preview. The first of these is the norm and the only one
considered in this article.
4. The target device
The type of device the data is targetted at can also be specified. However, the usual
case is that the data is device independent and this is the only case considered by this
article.

Windows encapsulates all this information inside a TFormatEtc structure, defined in the
ActiveX unit. Listing 3 reproduces the definitions.

type
tagFORMATETC = record
cfFormat: TClipFormat;
ptd: PDVTargetDevice;
dwAspect: Longint;
lindex: Longint;
tymed: Longint;
end;
TFormatEtc = tagFORMATETC;
Listing 3

The fields are discussed briefly in Table 3 below. See the Windows API help file for more
detailed information.

TFormatEtc methods
cfFormat Tells us about the data format and is a clipboard format identifier.
Informs about the target device and is a structure of type DVTARGETDEVICE.
ptd The value of this field is nil if the data is device independent. We will always set
this value to nil in this article.
The view aspect. In all the cases we will consider, this value will be
DVASPECT_CONTENT meaning we should represent the data as an embedded
dwAspect
object. Other possible values are DVASPECT_THUMBNAIL, DVASPECT_ICON
and DVASPECT_DOCPRINT.
Tells us when the view aspect must be split across page boundaries. Often, and in
lindex all cases considered here, lindex is -1 indicating that all the data should be
displayed.
Describes the media type of the storage medium. This is one of the TYMED_*
tymed
enumeration values discussed above.
Table 3

We will use TFormatEtc structures in one of two ways:

1. We will examine structures filled in by Windows to learn about a data object's format.
2. We will set up our own structures to request objects in a required format.

That concludes the discussion of data objects. Armed with this knowledge we can move on to
the next section where we discuss how to interogate, and extract data from, data objects.
How to receive data dragged from other
applications (part 4 of 6)
Querying and getting data from a data object

Querying the Data Object


We often need to query an object to find out about the formats it supports. There are two
distinct ways to do this. The first is to enquire whether the data object supports a specified
format and the second is to enumerate all the supported formats. We will consider each of
these in turn.

Checking for a specified data format

To check for a specific format we call the data object's QueryGetData method, passing it a
TFormatEtc structure that describes the required data format. Listing 4 defines a helper
routine that finds this information for a given clipboard format and storage medium.

function HasFormat(const DataObj: IDataObject;


const Fmt: TClipFormat; const Tymed: Integer): Boolean;
var
FmtEtc: TFormatEtc;
begin
FmtEtc.cfFormat := Fmt;
FmtEtc.ptd := nil; // device independent
FmtEtc.dwAspect := DVASPECT_CONTENT; // full detail
FmtEtc.lindex := -1; // all the data
FmtEtc.tymed := Tymed;
Result := DataObj.QueryGetData(FmtEtc) = S_OK;
end;
Listing 4

Here we pass a IDataObject instance to the helper function along with the required clipboard
format and storage medium type. We record the clipboard format and storage medium type in
a TFormatEtc structure and set its other fields to default values. Once we have set up the
TFormatEtc structure we pass it to the data object's QueryGetData method and check the
return value. A return of S_OK indicates the format is supported.

Enumerating all data formats

IDataObject can provide a standard Windows enumerator that iterates through all data
formats supported by a data object. The skeleton code in Listing 5 show how to use the
enumeration.

procedure EnumDataFormats(const DataObj: IDataObject);


var
Enum: IEnumFormatEtc;
FormatEtc: TFormatEtc;
begin
OleCheck(DataObj.EnumFormatEtc(DATADIR_GET, Enum));
while Enum.Next(1, FormatEtc, nil) = S_OK do
begin
// Do something with FormatEtc here
...
end;
end;
Listing 5

We first call the EnumFormatEtc method of the data object to get the required enumerator.
We pass the DATADIR_GET flag to the method to indicate that we want to know about data
formats we can read from the data object (rather the formats we could write to the object). If
all goes well the method passes the enumerator out via the Enum parameter. If the call fails
the OleCheck traps the error return and raises an exception.

Example code
One of the examples in this article's Demo Code uses this technique to list data formats in a
list view.

Once we have the enumerator we repeatedly call its Next method passing 1 as the first
parameter to indicate we want one TFormatEtc structure at each iteration. For as long as there
is more information available the method sets FormatEtc and returns S_OK. When the data
formats are exhausted the method returns S_FALSE and the loop ends. Within the while loop
we can do something with data formats, such as list them in a dialog box.

Getting Data from the Data Object


Once we know the data format we want, the easiest way to get data from the data object is via
its GetData method. The skeleton code presented in Listing 6 shows how to approach this.

procedure GetDataFromObj(const DataObj: IDataObject;


const Fmt: TClipFormat; const Tymed: Integer);
var
FmtEtc: TFormatEtc;
Medium: TStgMedium;
begin
FmtEtc.cfFormat := Fmt;
FmtEtc.ptd := nil;
FmtEtc.dwAspect := DVASPECT_CONTENT;
FmtEtc.lindex := -1;
FmtEtc.tymed := Tymed;
OleCheck(DataObj.GetData(FmtEtc, Medium));
try
Assert(Medium.tymed = FmtEtc.tymed);
// Do something with the data from Medium
...
finally
ReleaseStgMedium(Medium);
end;
end;
Listing 6

Just like in Listing 4 we must fill in the TFormatEtc structure with details of the required data
format as specified by the Fmt and Tymed parameters. Next we call the data object's GetData
method passing in the data format structure. If the method succeeds it returns S_OK and sets
Medium to reference the required storage medium. Medium should have the same value in its
tymed field as FmtEtc so we use an assertion to check this. If GetData fails it causes
OleCheck to raise an exception.

Now we have the storage medium we can then do something with it, for example display the
information it contains. Once we have finished with the storage medium we must release its
data structures. How this is done varies depending on the medium type. Fortunately Windows
provides the ReleaseStgMedium method that knows how to free the required data structures,
so we simply call that routine.

Now it's all very well saying "do something with the data from Medium", but what exactly is
that "something"? Well, the answer depends on both the data format and the media type. So
next we'll look at a few examples of working with different data formats and data types.

Example 1: Text stored in global memory

Several data formats provide their data as ANSI text stored in global memory. Examples are:

 CF_TEXT – a standard format.


 CF_FILENAMEA – a format defined by the shell for passing filenames.
 Rich Text Format
 HTML Format

Listing 7 shows an example of how to retrieve plain text data from global memory. The
function can be passed any clipboard format that uses plain text data and will return the text
as a string.

function GetTextFromDataObj(const DataObj: IDataObject;


const Fmt: TClipFormat): string;
var
FormatEtc: TFormatEtc;
Medium: TStgMedium;
PText: PChar;
begin
FormatEtc.cfFormat := Fmt;
FormatEtc.ptd := nil;
FormatEtc.dwAspect := DVASPECT_CONTENT;
FormatEtc.lindex := -1;
FormatEtc.tymed := TYMED_HGLOBAL;
OleCheck(DataObj.GetData(FormatEtc, Medium));
try
Assert(Medium.tymed = TYMED_HGLOBAL);
PText := GlobalLock(Medium.hGlobal);
try
Result := PText;
finally
GlobalUnlock(Medium.hGlobal);
end;
finally
ReleaseStgMedium(Medium);
end;
end;
Listing 7
The routine starts the same as Listing 6 except that we hard wire the TYMED_HGLOBAL
media type. The code that retrieves the text begins after the Assert statement. We lock the
global memory handle (accessed via a field of the TStgMedium structure returned by GetData
in Medium) and store the resulting pointer in PText. PText now points to the start of a zero
termimated string of ANSI characters, so we simply set the function result to that string.
Finally we unlock the memory handle and then call ReleaseStgMedium, which in turn frees
the storage medium's memory.

Example 2: File list stored in global memory

In article #11 we noted that the list of files dropped on a window was accessed via a HDROP
handle and we used the DragQueryXXX API functions to get at the list of files. Well, the
same principle applies for OLE drag and drop. The related clipboard format is CF_HDROP
and the medium type is TYMED_HGLOBAL. The drop handle is stored in the
TStgMedium.hGlobal field. Listing 8 is based on article #11's listing 7, but it gets its drop
handle from the data object.

procedure GetFileListFromObj(const DataObj: IDataObject;


const FileList: TStrings);
var
FmtEtc: TFormatEtc; // specifies required data format
Medium: TStgMedium; // storage medium containing file list
DroppedFileCount: Integer; // number of dropped files
I: Integer; // loops thru dropped files
FileNameLength: Integer; // length of a dropped file name
FileName: string; // name of a dropped file
begin
// Get required storage medium from data object
FmtEtc.cfFormat := CF_HDROP;
FmtEtc.ptd := nil;
FmtEtc.dwAspect := DVASPECT_CONTENT;
FmtEtc.lindex := -1;
FmtEtc.tymed := TYMED_HGLOBAL;
OleCheck(DataObj.GetData(FmtEtc, Medium));
try
try
// Get count of files dropped
DroppedFileCount := DragQueryFile(
Medium.hGlobal, $FFFFFFFF, nil, 0
);
// Get name of each file dropped and process it
for I := 0 to Pred(DroppedFileCount) do
begin
// get length of file name, then name itself
FileNameLength := DragQueryFile(Medium.hGlobal, I, nil, 0);
SetLength(FileName, FileNameLength);
DragQueryFile(
Medium.hGlobal, I, PChar(FileName), FileNameLength + 1
);
// add file name to list
FileList.Add(FileName);
end;
finally
// Tidy up - release the drop handle
// don't use DropH again after this
DragFinish(Medium.hGlobal);
end;
finally
ReleaseStgMedium(Medium);
end;
end;
Listing 8

This routine gets a list of dropped files from the the provided data object and stores the file
names in the routine's FileList string list parameter. It begins, as usual by populating a
TFormatEtc with the required information (this time requesting the CF_HDROP format and
TYMED_HGLOBAL storage medium). It then gets the data from the data object.

We pass the value of the storage medium's hGlobal field to DragQueryFile to get the number
of dropped files. Next we loop through all the dropped files getting the name of each file
name by making two more calls to the ever so flexible DragQueryFile function. The first call
gets the length of the filename, to enable us to allocate a string of the required length. The
second call reads the file name into the string. The file name is then added to the list. When
the loop finishes a call to DragFinish finalises the drop handle. Finally, we release the
storage medium's memory by means of the now familiar call to ReleaseStgMedium.

Example 3: Data stored in a stream

Most data objects you will encounter when handling drag and drop will make their data
available through a memory handle accessed via TStgMedium's hGlobal field. However you
will occasionally come across the other storage media types. We will take a very brief look at
just one more here – data streams accessed via the IStream interface.

Get the the required medium in the usual way, but specify TYMED_ISTREAM in
TFormatEtc's tymed field. Obtain the medium as normal by calling the data object's GetData
method. Once you have the storage medium cast it's pstm field to IStream and use the
methods of IStream to manipulate the data.

Now we have an understanding of how to manipulate data objects we are at long last in a
position to look at how to implement the IDropTarget interface. We do this in the next
section.
How to receive data dragged from other
applications (part 5 of 6)
Implementing IDropTarget

Implementing IDropTarget
We will illustrate how to implement IDropTarget by considering two examples.

In both examples we will implement IDropTarget in the main form. Since TForm implements
IUnknown we don't have to bother implementing its methods. Therefore all we need to do is
implement the IDropTarget methods.

Boilerplate code

Both examples share some boilerplate code, which is presented in Listing 9.

unit Form1;

interface

type
TForm1 = class(TForm, IDropTarget)
...
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
...
private
...
protected
{ IDropTarget methods }
function IDropTarget.DragEnter = DropTargetDragEnter;
function DropTargetDragEnter(const dataObj: IDataObject;
grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult;
stdcall;
function IDropTarget.DragOver = DropTargetDragOver;
function DropTargetDragOver(grfKeyState: Longint; pt: TPoint;
var dwEffect: Longint): HResult;
stdcall;
function IDropTarget.DragLeave = DropTargetDragLeave;
function DropTargetDragLeave: HResult;
stdcall;
function IDropTarget.Drop = DropTargetDrop;
function DropTargetDrop(const dataObj: IDataObject; grfKeyState:
Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
public
...
end;

implementation

function TForm1.DropTargetDragEnter(const dataObj: IDataObject;


grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
...
Result := S_OK;
end;

function TForm1.DropTargetDragLeave: HResult;


begin
...
Result := S_OK;
end;

function TForm1.DropTargetDragOver(grfKeyState: Integer; pt: TPoint;


var dwEffect: Integer): HResult;
begin
...
Result := S_OK;
end;

function TForm1.DropTargetDrop(const dataObj: IDataObject;


grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
...
Result := S_OK;
end;

procedure TForm1.FormCreate(Sender: TObject);


begin
OleInitialize(nil);
OleCheck(RegisterDragDrop(Handle, Self));
end;

procedure TForm1.FormDestroy(Sender: TObject);


begin
RevokeDragDrop(Handle);
OleUninitialize;
end;

end.
Listing 9

First of all notice that the class declaration for TForm1 includes IDropTarget. Further down
in the protected section we declare the methods of IDropTarget. We have used Delphi's
method resolution clauses to rename each of the methods. This has been done because TForm
already has a method called DragOver that clashes with, and gets hidden by, the method of
the same name in IDropTarget. Each of IDropTarget's methods should return S_OK to
indicate a successful completion.

Registering Drag-drop Implementations


You can register any object that implements IDropTarget as the drag-drop handler for any
window. It doesn't have to be the main form class or its window.

The remaining code are the handlers of the form's OnCreate and OnDestroy events –
FormCreate and FormDestroy. In FormCreate we first initialise OLE then call the
RegisterDragDrop API function to register our implementation of IDropTarget – in this case
our own form – as the drag-drop handler for the main window. Finally, in FormDestroy, we
unregister drag-drop for the window by calling RevokeDragDrop. Lastly we unitialise OLE.

Example 1: Listing data formats


In this first example we will list all the data formats supported by a data object dropped on a
form. Our main window will accept any dropped object and will enumerate its data formats
when dropped.

To begin, create a new project and add all the boilerplate code from Listing 9. Now drop a
TListView control on the form, name it "lvDisplay", set it's ViewStyle property to vsReport
and add four columns titled "Format", "Storage Medium", "Aspect" and "lindex".

Given that we will accept any object we will always display a copy cursor whenever an
object is dragged over the form. Furthermore, we won't take any notice of modifier keys and
have no need to examine the object until it is dropped. These observations lead us to
implement the DropTargetDragEnter, DropTargetDragLeave and DropTargetDragOver
methods as shown in Listing 10.

function TForm1.DropTargetDragEnter(const dataObj: IDataObject;


grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
dwEffect := DROPEFFECT_COPY;
Result := S_OK;
end;

function TForm1.DropTargetDragLeave: HResult;


begin
Result := S_OK;
end;

function TForm1.DropTargetDragOver(grfKeyState: Integer; pt: TPoint;


var dwEffect: Integer): HResult;
begin
dwEffect := DROPEFFECT_COPY;
Result := S_OK;
end;
Listing 10

All we are doing here is to get Windows to display the copy cursor by setting the dwEffect
parameter of DropTargetDragEnter and DropTargetDragOver to DROPEFFECT_COPY.
DropTargetDragLeave is left unchanged from the boilerplate code.

All the action takes place in DropTargetDrop as is shown in Listing 11.

function TForm1.DropTargetDrop(const dataObj: IDataObject;


grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
var
Enum: IEnumFormatEtc;
FormatEtc: TFormatEtc;
begin
OleCheck(DataObj.EnumFormatEtc(DATADIR_GET, Enum));
lvDisplay.Clear;
while Enum.Next(1, FormatEtc, nil) = S_OK do
DisplayDataInfo(FormatEtc);
dwEffect := DROPEFFECT_COPY;
Result := S_OK;
end;

procedure TForm1.DisplayDataInfo(const FmtEtc: TFormatEtc);


var
LI: TListItem;
begin
LI := lvDisplay.Items.Add;
LI.Caption := CBFormatDesc(FmtEtc.cfFormat);
LI.SubItems.Add(TymedDesc(FmtEtc.tymed));
LI.SubItems.Add(AspectDesc(FmtEtc.dwAspect));
LI.SubItems.Add(IntToStr(FmtEtc.lindex));
end;
Listing 11

First we set the cursor to DROPEFFECT_COPY and clear the list view. Next we get a data
format enumerator from the data object. Using the enumerator we get each supported data
format and display information from it's TFormatEtc structure using a subsidiary method,
DisplayDataInfo. Finally we return S_OK.

DisplayDataInfo simply adds a new item to the list view that displays information about the
FmtEtc structure passed to the method as a parameter. The following helper routines are used
to get the display information:

 CBFormatDesc – returns a description of the clipboard format specified by


FmtEtc.cfFormat. The function can handle both built-in and custom clipboard
formats.
 TymedDesc – returns the name of the TYMED_* constant that describes the value of
FmtEtc.tymed.
 AspectDesc – returns the name of the DVASPECT_* constant describing
FmtEtc.dwAspect.

These routines are not listed here because they are trivial and not relevant to the main subject.
However the routines are supplied with the associated demo program.

Example 2: Displaying data from selected data formats

This example is more like somethings you may need to implement in a real world application
– it checks the data object being dragged and decides whether it can accept the object or not.
If it can accept the object the program displays a textual representation of the data.

Our application will accept the following data object types, all of which must be provided via
a global memory storage medium:

 Plain text in the CF_TEXT format. Text dropped on the application will be displayed
in the main window.
 HTML code in the custom "HTML format". The HTML source code will be
displayed in full in the main window. The plain text version of the code will also be
displayed if it is available.

The program will also attempt to move the data from the source application if the Shift key is
held down. Otherwise if the either no or any other modifier key is held down the data will be
copied.
To begin with, start a new application and drop two TMemo controls onto the main form.
Arrange them one above the other and name the top one "edText" and the lower one
"edHTML". Now add the boilerplate code from Listing 9.

Before we get started on the code proper, recall that we will be handling "HTML Format"
data objects. Since this is a custom format we need to register it. We do this by declaring a
global variable named CF_HTML in the form unit's implementation section and by
registering the format with Windows in the initialization section, storing the format identifier
in CF_TEXT. Listing 12 illustrates the code.

...

implementation

...

var CF_HTML: TClipFormat; // identifier for HTML clipboard format

...

initialization

CF_HTML := RegisterClipboardFormat('HTML Format');

end.
Listing 12

We will begin by considering the IDropTarget.DragEnter method, implemented as


DropTargetDragEnter, as shown in Listing 13 below. The listing also shows two helper
methods called by DropTargetDragEnter.

function TForm1.DropTargetDragEnter(const dataObj: IDataObject;


grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
Result := S_OK;
fCanDrop := CanDrop(dataObj);
dwEffect := CursorEffect(dwEffect, grfKeyState);
end;

function TForm1.CanDrop(const DataObj: IDataObject): Boolean;


begin
Result := DataObj.QueryGetData(MakeFormatEtc(CF_TEXT)) = S_OK;
if not Result then
Result := DataObj.QueryGetData(MakeFormatEtc(CF_HTML)) = S_OK;
end;

function TForm1.CursorEffect(const AllowedEffects: Longint;


const KeyState: Integer): Longint;
begin
Result := DROPEFFECT_NONE;
if fCanDrop then
begin
if (KeyState and MK_SHIFT = MK_SHIFT) and
(DROPEFFECT_MOVE and AllowedEffects = DROPEFFECT_MOVE) then
Result := DROPEFFECT_MOVE
else if (DROPEFFECT_COPY and AllowedEffects = DROPEFFECT_COPY) then
Result := DROPEFFECT_COPY;
end;
end;
Listing 13

After setting the required return value in DropTargetDragEnter, the first thing we do is check
if the data object can be dropped, i.e. if it supports either of the data formats we are interested
in. We hand this decision off to CanDrop and store the result in the private fCanDrop field
for use later (in DropTargetDragOver).

CanDrop simply queries the data object to see if it supports the CF_TEXT data format. If it
doesn't the data object is queried again for the HTML format. If neither format is supported
false is returned. Note that CanDrop calls the convenience MakeFormatEtc method that
simply constructs an appropriate TFormatEtc structure to pass to
IDataObject.QueryGetData.

Back in DropTargetDragEnter the next thing we do is to determine the required cursor


effect. Again this is handed off to a helper method, CursorEffect. DropTargetDragEnter
passes the bitmask of allowed cursor effects, from its dwEffect parameter, along with the
keyboard and mouse state, from the grfKeyState parameter, to CursorEffect. This method
calculates the required cursor effect according to the following rules:

 If the data object does not support the required data formats return
DROPEFFECT_NONE.
 If the Shift key is pressed and the DROPEFFECT_MOVE effect is allowed return
DROPEFFECT_MOVE.
 If any other, or no, modifier keys are pressed, or if DROPEFFECT_MOVE is reqested
but not allowed, DROPEFFECT_COPY is returned.

Listing 14 shows the next IDropTarget method, DragOver, implemented as


DropTargetDragOver.

function TForm1.DropTargetDragOver(grfKeyState: Integer; pt: TPoint;


var dwEffect: Integer): HResult;
begin
Result := S_OK;
dwEffect := CursorEffect(dwEffect, grfKeyState);
end;
Listing 14

All we do here is set the cursor effect once again, using the current key state from the
grfKeyState parameter and the permitted cursor effects from dwEffect. The cursor state may
change between calls to this method because the pressed modifier keys may have changed.

Next up is DragLeave, implemented as DropTargetDragLeave. As is shown by Listing 15,


this method is unchanged from the boilerplate code.

function TForm1.DropTargetDragLeave: HResult;


begin
Result := S_OK;
end;
Listing 15
The final IDropTarget method to consider is Drop, implemented as DropTargetDrop. Listing
16 shows this method along with helper methods that are used to display the data.

function TForm1.DropTargetDrop(const dataObj: IDataObject;


grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
Result := S_OK;
fCanDrop := CanDrop(dataObj);
dwEffect := CursorEffect(dwEffect, grfKeyState);
DisplayData(dataObj);
end;

procedure TForm1.DisplayData(const DataObj: IDataObject);


begin
edText.Text := GetTextFromObj(DataObj, CF_TEXT);
edHTML.Text := GetTextFromObj(DataObj, CF_HTML);
end;

function TForm1.GetTextFromObj(const DataObj: IDataObject;


const Fmt: TClipFormat): string;
var
Medium: TStgMedium;
PText: PChar;
begin
if DataObj.GetData(MakeFormatEtc(Fmt), Medium) = S_OK then
begin
Assert(Medium.tymed = MakeFormatEtc(Fmt).tymed);
try
PText := GlobalLock(Medium.hGlobal);
try
Result := PText;
finally
GlobalUnlock(Medium.hGlobal);
end;
finally
ReleaseStgMedium(Medium);
end;
end
else
Result := '';
end;
Listing 16

DropTargetDrop starts by re-checking data object to see if we can handle it and then sets the
drop cursor. Finally it calls DisplayData to display the data from the dropped data object.

DisplayData simply sets the text of the two memo controls to the strings returned from
GetTextFromObj. GetTextFromObj is called twice, once to retrieve any plain text
(CF_TEXT) and again to retrieve any HTML Format (CF_HTML) data as text from the data
object. GetTextFromObj will return the empty string if the requested data format is not
available.

GetTextFromObj extracts text data from the data object in the format specified in its Fmt
parameter. It does this by calling MakeFormatEtc to create a TFormatEtc structure that
describes the desired clipboard format. This structure is passed to the data object's GetData
method. If the method returns a value other than S_OK then the requested format is not
supported and the empty string is returned. Otherwise the now familiar code to retrieve text
from a global memory handle is executed and the text is returned.

All that remains to do now is to implement the MakeFormatEtc helper method referred to
above. The method is presented in Listing 17 below.

function TForm1.MakeFormatEtc(const Fmt: TClipFormat): TFormatEtc;


begin
Result.cfFormat := Fmt;
Result.ptd := nil;
Result.dwAspect := DVASPECT_CONTENT;
Result.lindex := -1;
Result.tymed := TYMED_HGLOBAL;
end;
Listing 17

All the method does is set up and return a TFormatEtc structure that requires a
TYMED_HGLOBAL medium type for the clipboard format passed in the method's Fmt
parameter. The code should be familiar by now.

Our examination of how to catch data dragged and dropped from other applications is now
complete. In the last section we will wrap up the article and provide a link to the article's
source code.
How to receive data dragged from other
applications (part 6 of 6)
A demo program to accompany this article is available for download.

The demo includes the complete source to the two examples presented in the previous
section.

The code was developed using Delphi 7 Professional, but may compile with earlier versions
and later Win32 personalities of the compiler, although this has not been tested.

This source code is merely a proof of concept and is intended only to illustrate this article. It
is not designed for use in its current form in finished applications. The code is provided on an
"AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. The
source code is released under the same Creative Commons License as this article. If you
agree to all this then please download the code using the following link.
Download the demo code

Going Further
While the code presented in this article works fine, it does rather clutter up the code of the
main form. A more tidy, but slightly more complex, solution is to implement IDropTarget in
a separate class and to call back to the main program to determine whether a dragged object
can be dropped and how to configure the drag cursor. It is also possible to wrap up the code
that interogates and reads data objects into a separate class.

My GUI for the PasH Pascal Highlighter program uses OLE drag-drop handling and isolates
the IDropTarget implementation in its own class. If you are interested in this approach please
feel free to download and examine the program's source code.

Summary
This article has provided an introduction to working with OLE Drag and Drop and has shown
how to implement IDropTarget and how interogate and extract some kinds of data from a
data object via its IDataObject interface. The article also showed how to register a window to
receive OLE drag-drop notifications by associating an IDropTarget implementation with the
window.

In addition, a demo providing source code of the two examples was also made available.

Finally some suggestions were made about to how to improve the code by isolating the
IDropTarget code in its own class.

References
The Windows API help file provided with Delphi proved invaluable in writing this article, as
did an in depth article published, I believe, on UNDO. Unfortunately I haven't been able to
find a recent working link to this article.

Feedback
I hope you enjoyed this article and found it useful. If you have any observations, comments
or have found any errors please contact me.