INPRISE Online And ZD Journals Present:
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" in the
September issue of C++Builder Developer's Journal.)
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 of C++Builder
Developer's Journal, 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. (You can download
our sample project files from www.zdjournals.com/cpb.) 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. (We've marked the code you need to enter in color.) 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).
Listing A: DDMAIN.H
//---------------------------------------------
#ifndef DDMainH
#define DDMainH
//---------------------------------------------
#include
<vcl\Classes.hpp>
#include
<vcl\Controls.hpp>
#include
<vcl\StdCtrls.hpp>
#include
<vcl\Forms.hpp>
#include
<shellapi.h>
//---------------------------------------------
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
<vcl\vcl.h>
#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);
}
Kent Reisdorph is a senior software engineer at TurboPower Software 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 kentr@turbopower.com.