How To Catch Files Dragged and Dropped On An Application From Explorer
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.
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.
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
DragAcceptFiles(Form1.Handle, True);
Listing 2
Listing 3
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
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:
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:
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:
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:
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;
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:
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.
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:
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:
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
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.
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
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
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.
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.
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.
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.
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.
Several data formats provide their data as ANSI text stored in global memory. Examples are:
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.
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.
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.
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
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
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.
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.
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.
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.
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:
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.
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
...
...
initialization
end.
Listing 12
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.
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.
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.
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.
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.