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."
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.
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.
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.
Listing A: DDMAIN.H
//--------------------------------------------- #ifndef DDMainH #define DDMainH //--------------------------------------------- #includeListing B: DDMAIN.CPP#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
//--------------------------------------------- #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.