September 1997

What a drag!

by Kent Reisdorph

Let's face it: As computer users we live in a drag-and-drop world. Sure, drag-and-drop is a buzz phrase, but this technique is also very useful. When I get a new software package, it isn't long before I find myself trying to drag and drop some element of the program. If dragging and dropping works, I'm happy and on my way again. If it doesn't work, I grumble a little. To produce high-quality applications, you need to implement the features users have come to expect in recent years--and drag-and-drop is definitely one of those features. VCL offers a certain degree of drag-and-drop capability. For example, you can drag and drop from one component to another on a form. You can even drag from a component on one form to a component on another form. However, VCL lacks support for dragging and dropping files from Windows Explorer to an application.

In this article, we'll demonstrate how you can let your C++Builder applications accept files dropped from applications such as Windows Explorer, the Find Files dialog box, or the Windows Desktop. In the course of our discussion, we'll examine the Windows API functions DragAcceptFiles() and DragQueryFile(), along with the WM_DROPFILES message. For a discussion of dragging and dropping between VCL components, see "A Drop in the Bucket."

Laying the groundwork

You'll probably be glad to learn that implementing drag-and-drop in your C++Builder applications is quite simple. You just need to perform three tasks:
  1. Tell Windows that your application will accept dropped files.
  2. Catch and handle the WM_DROPFILES message.
  3. Process the dropped files.
The third step certainly requires the most work. Let's examine these steps individually so you'll have a clearer picture of what's required to support drag-and-drop in your applications.

Receiving

The first step is to tell Windows that you'll accept dropped files. To do so, simply call the DragAcceptFiles() function. This function is prototyped in SHELLAPI.H, so you'll need to include that header in any source code units that use the drag-and-drop functions. After you've included the header, you need to call DragAcceptFiles() as follows:

DragAcceptFiles(Handle, true);

The first parameter of DragAcceptFiles() specifies the window handle of the window that will receive dropped files. Once you've called DragAcceptFiles(), two things happen. First, the mouse cursor will automatically change to the drag cursor when you drag files over the window. Second, the window will receive a WM_DROPFILES message when you drop the dragged files on the window. Windows takes care of these details for you automatically, so you don't have to do anything more than register the window and catch the WM_DROPFILES message.

The second parameter of the DragAcceptFiles() function is a Boolean value that specifies whether the window will accept dropped files. To accept dropped files, pass true for this parameter. To stop accepting dropped files, call DragAcceptFiles() again with this parameter set to false.

Your program may or may not accept dropped files depending on different states, so it's important to be able to turn off drag-and-drop capabilities. You can call DragAcceptFiles() at almost any time, but your form's OnCreate event handler is a good place to initially register your application to accept dropped files.

The window that will accept dropped files can be either a form or a specific component on a form. For example, you may want only a Memo component to accept dropped files, rather than the entire form. In this case, you can pass the Handle property of the Memo component to DragAcceptFiles(). However, doing so won't help you much unless you create a component derived from TMemo, which processes the WM_DROPFILES message. Creating a component is necessary because the window handle passed to DragAcceptFiles() is the window handle that will receive the WM_DROPFILES message when you drop files. If you make the form the recipient of dropped files, then your life will be a little easier. The lesson here is to add drag-and-drop support for individual components only when necessary, since doing so involves extra work.

Catch the wave

Now that you've told Windows you'll accept dropped files, you need to be prepared to catch the WM_DROPFILES message. VCL doesn't provide an event for processing this message, so you'll have to do it the hard way (which, thankfully, isn't too hard). You'll take the easy route and have the main form of your application accept dropped files. In order to catch the WM_DROPFILES message, you'll create a message map table in the form's class declaration. We discussed message maps in our premier issue in the article "Incorporate Custom Message-Handling in Your Applications." Rather than cover that same ground, we'll give you a quick overview. The class declaration for a form that handles the WM_DROPFILES message would look like this:
 class TForm1 : public TForm
 {
 __published:    // IDE-managed Components
 private:        // User declarations
   void __fastcall WmDropFiles(
   TWMDropFiles& Message);
 public:         // User declarations
   __fastcall TForm1(TComponent* Owner);
 BEGIN_MESSAGE_MAP
   MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, WmDropFiles)
 END_MESSAGE_MAP(TForm)
 };
Notice the declaration for the WmDropFiles() function in the private section and the message map in the public section. The WmDropFiles() function is our message handler for the WM_DROPFILES message, and the message map table tells the application to call the message handler when that particular message is received.

Getting the drop on the competition

Next, you need to create the function that handles the WM_DROPFILES message. In the previous section, you declared the WmDropFiles() function--now you'll define that function. First, look at this minimal message handler for the WM_DROPFILES message:
 void __fastcall TForm1::WmDropFiles(TWMDropFiles& Message)
 {
   char buff[MAX_PATH];
   HDROP hDrop = (HDROP)Message.Drop;
   int numFiles = 
      DragQueryFile(hDrop, -1, NULL, NULL);
   for (int i=0;i < numFiles;i++) {
      DragQueryFile(hDrop, i, buff, sizeof(buff));
      // process the file in 'buff'
   }
   DragFinish(hDrop);
 }
Let's examine this code one step at a time. You begin by declaring a character array that will hold the filenames you retrieve from Windows. The character array is declared using size MAX_PATH (260) because no filename will be longer than that.

Next comes a call to DragQueryFile(), which gets the number of files dropped. (For more information on this function, see the sidebar "The DragQueryFile() Function.") Now, a for loop retrieves each filename. Notice that the DragQueryFile() call inside the for loop passes the value of i in the iFile parameter.

After the call to DragQueryFile(), the variable buff contains the name of the file corresponding to that index. The first time through the loop, buff will contain the name of the first file dropped; the second time, it will contain the name of the second file dropped; and so on. At this point, you'd do something with each filename as it's retrieved from Windows.

Finally, notice that at the end of the function you call the DragFinish() function, passing the hDrop handle. DragFinish() frees the memory that Windows allocated for the dropped-files information. Without this call, your application will leak a little memory each time you drop files.

Final thoughts

Let's look briefly at one other function that pertains to drag-and-drop operations. You can use the DragQueryPoint() function to determine the mouse cursor's location when files were dropped. Doing so lets you handle the drag-and-drop operation at the form level but use the mouse cursor point to determine which component the mouse was over when the files were dropped. Listings A and B contain a program that implements drag-and-drop in a C++Builder program. 

Listing A: DDMAIN.H

//---------------------------------------------
#ifndef DDMainH
#define DDMainH
//---------------------------------------------
#include 
#include 
#include 
#include 
#include 
//---------------------------------------------
class TForm1 : public TForm
{
  __published:    // IDE-managed Components
    TMemo *Memo1;
    void __fastcall FormCreate(TObject *Sender);
  private:        // User declarations
    void __fastcall
    WmDropFiles(TWMDropFiles& Message);
  public:         // User declarations
    __fastcall TForm1(TComponent* Owner);
  BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, WmDropFiles)
  END_MESSAGE_MAP(TForm)
};

//---------------------------------------------
extern TForm1 *Form1;
//---------------------------------------------

#endif
Listing B: DDMAIN.CPP
//---------------------------------------------
#include 
#pragma hdrstop

#include "DDMain.h"
//---------------------------------------------

#pragma resource "*.dfm"
TForm1 *Form1;

//---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
//---------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  DragAcceptFiles(Handle, true);
}
//---------------------------------------------
void __fastcall TForm1::WmDropFiles(TWMDropFiles& Message)
{
  char buff[MAX_PATH];
  volatile int x = -1;
  HDROP hDrop = (HDROP)Message.Drop;
  int numFiles = DragQueryFile(hDrop, -1, NULL, NULL);
  Memo1->Lines->Clear();
  for (int i=0;i < numFiles;i++) {
    DragQueryFile(hDrop, i, buff, sizeof(buff));
    if (numFiles == 1) 
      Memo1->Lines->LoadFromFile(buff);
    else 
      Memo1->Lines->Add(buff);
  }
  DragFinish(hDrop);
}
The program, which consists of nothing more than the main form and a Memo component, lets you drag and drop a text file onto the application. When you drop a single file, the Memo component displays the contents of the file. If you drop several files on the application, the Memo component lists the files dropped.

To create this program, place a Memo component on a form. Now, enter the code from Listings A and B into the source unit and header as required. Compile and run the program. Then, experiment with dragging and dropping several files at a time and also with individual text files (source code files will work too, of course).

Kent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.