A Delphi File-Drop Component 

by Paul Sobolik



A Delphi component has three levels, each aimed at a different
potential user. These levels correspond exactly with the
visibility of the component's parts. 



The first level is aimed at the application builder, and
consists of the public (and published) parts of the component.
At this level, the component really seems like a physical
object; The application builder picks it up from the palette and
plops it onto a form. It may have properties to customize its
appearance or behavior, but the component will function in some
way as it is, without any effort on the part of the application
builder.



The second level is made up of the properties and methods in the
protected part of the component's class definition. This level
is aimed at programmers who will use the component in a modified
form by subclassing it. This is probably the trickiest level to
design, because it involves anticipating how the component might
be used other than the way you intended, or at least guessing
what aspects of it are likely candidates for modification.
Designing in hooks to facilitate future modifications will make
your component that much more useful.



The third level of a Delphi component is the private section.
This level doesn't really have an "audience" like the other two
levels; It ain't nobody's business but your own. This is where
the logical interfaces of the other levels are implemented.
Writing this section is more like typical Pascal or C
programming: Library, API and other functions are called; bits
are twiddled; things get done.



TShellDropPanel is a simple and useful example of a Delphi
component. A TShellDropPanel descends from the standard Visual
Control Library component TPanel, with the added capability of
being able to accept files dropped from the Windows File
Manager. 



When the application builder places a TShellDropPanel onto a
form from the Delphi component palette, it is more-or-less
indistinguishable from a TPanel: It has a variety of border
options, can display text, contain other components and so
forth. (It even inherits Delphi objects' built-in drag and drop
abilities, facilitating communication between other components
on the form.) A close look at the object browser, though, shows
that one property and one method have been added.



The published property Accepting is of the standard Pascal type
Boolean. Setting this property to True indicates to the system
that the TShellDropPanel will accept files dropped from the File
Manager; Setting it to False means that it won't accept such
files. The File Manager changes the mouse cursor to an
appropriate picture when the user drags a file or a list of
files over a window that has given notification that it was
accepting files. This lets the user know that files can be
dropped there. If the user goes ahead and drops the files on the
TShellDropPanel, the component's OnDropFiles method is called,
if one is defined. The OnDropFiles method is TShellDropPanel's
other new published property. OnDropFiles is of the user-defined
type TDropFilesEvent, which is defined as follows:



TDropFilesEvent = procedure(Sender : TObject; Files :
TStringList; Location : TPoint) of Object;



This method's parameters contain information about the file-drop
event. Sender is the TObject that triggered the event, which
will nearly always be the TShellDropPanel itself. Files is a
TStringList that contains a list of the files that were dropped.
It's up to the application builder to do something with this
list during this method call. If the list needs to be stored, it
must be copied because the TStringList indicated by Files is
destroyed after the method returns. The last parameter,
Location, is a TPoint that indicates precisely where in the
TShellDropPanel the mouse pointer was when the files were
dropped.



And that is it. 



A simple program is included to demonstrate the use of a
TShellDropPanel. SDRPTEST.DPR contains the project source;
MAINFORM.DFM is the main form description file; and MAINFORM.PAS
is the source code for the form. 



The demo program's main window has a check box labeled "Accept
Dropped Files" at the top, and a panel containing a list box
below. When it is run, you can click the check box and then drag
files from the File Manager and drop them on the list box. The
program will display the names of the files you dropped in the
list box.



Turning to the demonstration program code shows that it consists
of a single form, FormMain, that holds a TCheckBox and a
TShellDropPanel. The TShellDropPanel in turn contains a
TListBox. The TListBox's Align property is set to alClient, so
that it completely fills the client area of the TShellDropPanel.
In action, this gives the impression that it is the list box
that is accepting the dropped files. 



Being a simple demonstration, FormMain has only four procedures,
each consisting of a single line. SetAccepting is a private
procedure that sets the TShellDropPanel's Accepting property to
the match the state of the TCheckBox--True if the box is
checked, False if it isn't. FormCreate is called when the form
is first created and CheckBox1Click when the TCheckBox is
clicked. Both of these procedures just call SetAccepting to keep
the states of TCheckBox and TShellDropPanel synchronized.
Finally, there is the procedure ShellDropPanel1DropFiles, which
is the TShellDropPanels's OnFileDrop method. This procedure sets
the TListBox's Items property to the list of files. 



TShellDropPanel's second-level interface is pretty thin. It's
difficult to imagine what further functionality such a
specialized component could want for. Nevertheless,
TShellDropPanel does have a protected procedure, DropFiles,
intended as a hook for future decendents. The low-level function
that responds to the user's file-dropping calls DropFiles
instead of calling the OnDropFiles event directly. DropFiles
just verifies that the OnDropFiles handler is defined and then
calls it with the appropriate parameters. The subclassing
programmer can change the behavior of the component by
overriding this dynamic procedure--to modify or filter the list
of files, for example--and then calling the OnDropFiles method.
Thus, the behavior of the component could be modified at a
pretty low level, without the programmer needing to understand
the Windows drag and drop protocol.



The rest of the implementation of TShellDropPanel is in the
private section. This section contains all of the awkward little
details involved in dealing with files dropped from the File
Manager. If you only want to use this component to implement
File Manager drag and drop capabilities, you don't need to worry
about these details or read any further. If you desire an
understanding of how TShellDropPanel works, read on:



The actual storage of the published properties Accepting and
OnDropFiles is in the private section of TShellDropPanel, in
class variables named FAccepting (a Boolean) and FOnDropFiles (a
TDropFilesEvent). This separation is only logical in the case of
OnDropFiles. The definition of this property, 



property OnDropFiles : TDropFilesEvent read FOnDropFiles write
FOnDropFiles;



indicates that when it is read or written, this property
accesses FOnDropFiles directly. 



Accepting, on the other hand, is defined as follows:



property Accepting : Boolean read FAccepting write SetAccepting;



When it is read, Accepting simply returns the value in
FAccepting, but the function SetAccepting is called to write to
the value. This feature of Delphi allows a property assignment
or dereference to have any side effects desired. In the case of
TShellDropPanel, changing the value of Accepting also triggers a
call to DragAcceptFiles, the Windows API function that tells the
system whether the component is accepting dropped files or not.
SetAccepting first verifies that the new parameter is different
from the current value of FAccepting in order to avoid an
unneccesary and time-consuming API call. If the call means a
change in the Accepting state, SetAccepting sets FAccepting
accordingly and then calls DragAcceptFiles. (The parameter
Handle is the Windows handle of the component, by the way. It
comes from TWinComponent, a distant ancestor of TShellDropPanel.)



Windows notifies a window that has indicated its desire to
accept dropped files that some files have been dropped by
sending it a WM_DROPFILES message.  TShellDropPanel defines a
special "message-handling method", WMDropFiles to deal with this
message. MDropFiles is defined as follows:



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



The Delphi component library's message-dispatching system will
see that this method is called in response to the WM_DROPFILES
message. The parameter type, TWMDropFiles, is a sort of
message-cracker defined in MESSAGES.DCU to simplify decoding the
message parameters. Even though WMDropFiles has no known
ancestral procedure, Delphi allows a call to the inherited
message-handler. Delphi's message-handler method system is
unique; Notice that the ancestral procedure is invoked just
using the keyword inherited, without specifying the method name
or parameters. The library sees to it that the call is
dispatched or handled correctly. After that business,
WMDropFiles calls the private method, DoDropFiles.



DoDropFiles is the meatiest method in this component. This
procedure first ensures that the drop occured in the client area
of the TShellDropPanel. It then creates a temporary TStringList
to hold the list of dropped files. 



A common error when using Delphi components like TStringList
comes from misuderstanding the what it means to declare a
variable of a TObject-decended class. It is important to
understand that the declaration



var lstFiles : TStringList;



doesn't create a TStringList object! What it creates is rather a
reference to a TStringList object; That is, a variable that can
hold a TStringList. The actual list is only created when one of
its constructors is called. DoDropFiles accomplishes this with
the line



lstFiles := TStringList.Create;



DoDropFiles then calls the Windows API function DragQueryFiles
to determine the number of files that were dropped on the
TShellDropPanel. That same function is then called in a loop to
fetch the actual names of the files, which are added to the
string list. When all the file names have been retrieved,
DropFiles is called, which eventually leads to the OnDropFiles
event handler.



Because DoDropFiles can't predict what might happen during the
call to the OnDropFiles event handler, it practices some
defensive techniques. Two try...finally blocks surround blocks
of the DoDropFiles method. These blocks ensure that even if an
exception occurs within the block, things will be cleaned up
appropriately. The first block makes sure the Windows API
function DragFinish is called, so that the system will release
resources allocated as part of the drag and drop interface. The
inner block sees to it that memory allocated for the TStringList
is freed.



The last procedure in TShellDropPanel is another
message-handling method. WMDestroy responds to the WM_DESTROY
message by setting the TShellDropPanel's Accepting property to
False. Recall that this leads to a call to DragAcceptFiles
telling the system that the window will not be accepting anymore
files. It may seem like this could be more efficiently
accomplished in a TShellDropPanel destructor. It seemed that way
to me, anyway, until I realized that that was way too late; The
window handle associated with the component is destroyed long
before the component is. 



Using a component-based programming language like Delphi
requires a broadening of your perspective. Writing reusable code
is still a goal, but there are more levels on which the code can
be reused. To produce the most useful Delphi components, it
helps to think of three potential users: The application builder
who will drop your component on his form without a care for how
it works; The component subclasser who will derive a new
component from yours, changing or modifying its properties; and,
finally, you, who must make it all work.

