Delphi Tutorial
Home ] Up ] Resume ] [ Delphi Tutorial ]

 

hndshak3.wmf (10422 bytes)
Drag and Drop from File Manager
by Eric Kundl
Copyright ©1995 - Eric Kundl

reprinted from: The Unofficial Newsletter of Delphi Users - Issue #10 - December 12th, 1995


Delphi allows dragging and dropping within the application easy enough, most components have a dragmode property. But on a small app I was writing I wanted to be able to go into FileManager and drop a filename on the app and have the app start up with the dropped file...OR... if the app was already running, to grab a handful of files (in a greedy sort of way) from FileManager and drop the whole truckload on the apps form. I have seen it done in other apps, how hard could it really be?

After a lot of digging through the Delphi Help I found it was not very hard at all. Here is what I have discovered.

The ability to drag and drop onto a running app is possible through the Windows API. The Delphi provided SHELLAPI unit has the 3 functions needed allow drag-drop functionality.

They are:

DragAcceptFiles Registers whether a window accepts dropped files
DragQueryFile Retrieves the filename of a dropped file
DragFinish Releases memory allocated for dropping files
There is also a function not specifically needed to accept files:
DragQueryPoint Retrieves the mouse position when a file is dropped

In addition, there is a windows message that your app needs to respond to that is sent when you release the mouse button while dragging: WM_DROPFILES.

First, add SHELLAPI to the units list of your form. Next, your app needs to tell Windows that the form is accepting dropped files. The FormCreate Event of your form would need the following code:

procedure TForm1.FormCreate(Sender: TObject);
begin
    DragAcceptFiles(Form1.Handle, true);
    Application.OnMessage := AppMessage;
end;

Here the first line calls the DragAcceptFiles function, passing the Handle of the form, and the boolean true that sets up drag-drop for the form. The second line allows your App to have its own message handler. In this case you are going to write a procedure named AppMessage, that will trap a Windows message before Windows itself processes it. In your case, you want to trap the WM_DROPFILES message.

Note that the DragAcceptFiles function call uses the Handle of the Form. If you want to have your app accept dropped files when it is minimized, you must add another call to DragAcceptFiles and pass the applications handle. ie: DragAcceptFiles(Application.Handle, true);

Thats all there is to setting things up. Next you need to write your own message handler, in this case named AppMessage.

The OnMessage event occurs when your application receives a Windows message. By creating an OnMessage event handler in your application, you can intercept specific windows messages and handle them yourself. Any message that you do not handle yourself is dispatched and Windows will handle it.

You need to write this procedure yourself, there is no event on the Object Inspector to click on that will cause Delphi to create it for you. So you need to create this in the implementation section of the forms code. And don't forget to add the procedure header to the object definition.

The procedure header would look like:
    procedure AppMessage(var Msg: Tmsg; var Handled: Boolean);

Your message handler procedure would look something like this:

procedure TForm1.AppMessage(var Msg: Tmsg; var Handled: Boolean);
    const
        BufferLength : word = 255;
    var
        DroppedFilename : string;
        FileIndex : word;
        QtyDroppedFiles : word;
        pDroppedFilename : array [0..255] of Char;
        DroppedFileLength : word;
begin
   if Msg.Message = WM_DROPFILES then
    begin
        FileIndex := $FFFF;
        QtyDroppedFiles := DragQueryFile(Msg.WParam, FileIndex,
                                                                pDroppedFilename, BufferLength);
        for FileIndex := 0 to (QtyDroppedFiles - 1) do
        begin
            DroppedFileLength := DragQueryFile(Msg.WParam, FileIndex,
                                                                      pDroppedFilename, BufferLength);
            DroppedFilename := StrPas(pDroppedFilename);

            ..... do something with DroppedFilename ....

        end;
        DragFinish(Msg.WParam);
        Handled := true;
    end;
end;

In this code, only the Msg.Message WM_DROPFILES is intercepted, which occurs when you drop one or more files from FileManager onto the window (or app, if you included a DragAcceptFiles for the Application.Handle).

If something is dropped, then you call DragQueryFile to process what was dropped, and before you leave, call DragFinish to free up memory used and set Handled to true to let Windows know you used the message yourself.

The Msg.WParam parameter is the Word parameter passed to the application in the wParam parameter of the WM_DROPFILES message.

The DragQueryFile function has 2 uses, based on the value of the second parameter. If FileIndex is a -1 ($FFFF), then it returns the qty of files dropped. If the value of the FileIndex parameter is between zero and the total number of files dropped, DragQueryFile copies the filename corresponding to that value to the buffer pointed to by the pDroppedFilename parameter. Note that the FileIndex values are offset from 0, so the first filename is FileIndex = 0 and the last filename is FileIndex = (qty of files dropped) minus 1.

The third parameter is the buffer that filenames will be placed into. Note that it is not a pascal string, but since we are dealing with the Windows API, is a null terminated string. The fourth parameter is the length of the filename buffer we are providing.

And that is it. Fairly simple, although it took a number of hours of searching books, and particularly the Delphi On-Line help. A few things I have noticed:

  1. When dropping files, the DroppedFilename is the complete path, not just the filename.ext
  2. It is possible to drag and drop just a directory. So if you are expecting filenames, you have to check for existence yourself.
  3. The filenames come in uppercased.

I have included an example program that shows how to get filenames into the app by:

  1. dropping filename(s) from filemanager onto the running app, as described above.
  2. dropping filename(s) from filemanager onto the minimized apps icon.
  3. dropping a filename from filemanager onto the non-running apps filename also in filemanager.
  4. running the app and specifying the filenames as parameters on the run command.
  5. using the OpenDialog to select filename(s) that are passed in a stringlist.

Anything dragged and dropped is dumped into a listbox.
As a bonus to all this digging, I found that the OpenDialog component allows:

  1. selecting more than one file: set the property Options|ofAllowMultiSelect to true.
  2. keeping a list of all previously selected files for the user to reselect from: set the property FileEditStyle to fsComboBox.
  3. remembering the path of the last selection, and starting the next selection from this path: set the property Options|ofNoChangeDir to false.
  4. and what may be a bug: if you select multiple files on the root directory, the filenames have an extra backslash(?).

So there you have the sum total of a week of my evenings. With just a small amount of code, your apps can quickly become FileManager friendly.

Copyright ©1995 - Eric Kundl