Drop Everything: How to Make Your Application Accept and Source Drag-and-Drop Files

Jeffrey Richter

Jeffrey Richter developed MicroQuill, Inc.'s Window DeMystifiers tool set and is the author of Windows 3: A Developer's Guide (M&T Publishing, 1991). He can be reached via his CompuServe address: 70444,24.

{ewc navigate.dll, ewbutton, /Bcodeview /T"Click to open or copy the code samples from this article." /C"samples_3}

It is more natural for users to concentrate on data instead of the programs required to process data. New technology in the MicrosoftÒ WindowsÔ operating system version 3.1 such as Object Linking and Embedding and drag-and-drop encourages users to think this way. Probably the easiest way to get the feel of drag-and-drop is by using the Windows 3.1 File Manager.

Many end users have not had an easy time understanding the file system of the MS-DOSÒ operating system with its directory structures and hierarchies. The File Manager is meant to allow users to manipulate files productively and efficiently. The Windows1 version 3.0 File Manager fell short of this goal, so it was rewritten for Windows 3.1. The new File Manager is so much better, it’s my first choice for performing file operations.

Without a doubt, the feature that makes the new File Manager so easy to use is its drag-and-drop capabilities. Drag-and-drop simplifies several common tasks. First, you can copy and move files from one directory to another. Figure 1 shows the File Manager with the contents of the C:\WIN31 directory displayed in the right panel. To move the COW.BMP file from this directory to the MAY92 directory, you simply select COW.BMP with the mouse, drag the mouse cursor so that it is on top of the MAY92 file folder icon in the left panel, and release the mouse button.

Figure 1 Moving a file.

Wherever the mouse is when its button is released is where the file will be moved or copied. The File Manager assumes that you are copying a file if you position the mouse cursor over a target directory on a drive other than the source file’s drive. It assumes that the file is to be moved if the mouse is positioned over a directory present on the same drive as the selected file.

The File Manager changes the shape of the cursor as items are being dragged. If the cursor is over any location that does not allow a file to be dropped (like the menu bar), File Manager changes the cursor to look like Figure 2. If the cursor is over a location that will cause the file to be moved, the cursor looks like Figure 3. If the file will be copied, the cursor looks like Figure 4. The plus sign in a cursor indicates that an additional file will be created.

You can select several files at once using the Ctrl key and drag them all with the mouse. For multiple files, the cursors shown in Figures 5 and 6 are used. To add several files to a group in the Program Manager, you could simply drop the files onto the desired group window. The Program Manager simply retrieves all of the icons for the dropped filenames and displays them in the group window.

Figure 2 Can't drop here.

Figure 3 Move file.

Figure 4 Copy file.

Figure 5 Move files.

Figure 6 Copy files.

There is another drag-and-drop operation available in the File Manager. Figure 7 shows a bitmap file being opened for editing by dropping it onto PBRUSH.EXE. When the mouse button is released, the File Manager will construct the following command line and then execute it:

C:\WIN31\PBRUSH.EXE C:\WIN31\LEAVES.BMP

Figure 7 Dropping onto a program starts it up and opens the dropfile.

It is also possible to select a file in the File Manager, drag it outside of the File Manager’s window, and drop it onto another application. When you drop files from the File Manager onto an application running as an icon, the application opens the file dropped onto it.

In Figure 8, the MOOHEAD.BMP filename was dragged from the File Manager and dropped into Write’s client area. Write automatically created a "package" for the dropped filename and inserted the package into the document at the location of the caret. This package is an embedded or linked object identified by a bitmap or icon. Packages can be embedded only into applications that support the OLE protocol. If, say, a TXT file were dropped into Write instead, Write would display the icon for Notepad.

Figure 8 Objects were embedded using drag-and-drop.

You could also drag a WAV file into Write. Whenever the user double-clicked on this package, the Windows sound recorder would activate and play the recorded audio in the WAV file (see Figure 8). As of Windows 3.1, Write is OLE-aware; Notepad isn’t. If a BMP or WAV file is dropped into Notepad, Notepad simply tries to open the file as an ASCII text file--most likely with less-than-acceptable results.

The icon chosen for an embedded package is determined by the association list for file extensions. The File Manager maintains a list of extensions and the pathnames of the programs that can operate on each file type. This list has been a part of Windows ever since the Windows 1.0 days. It allows the user to double-click on a data file from the File Manager or MS-DOS Executive (pre-Windows 3.0) to cause the proper application to execute and load the data file automatically.

File extensions can be associated with applications from the Associate dialog box in the File Manager.

Becoming a Dropfile Client

Modifying your application so that it is a dropfile target is extremely easy. There are only four new Windows API functions, one new message, and one new window style. To use the new APIs, you must include the SHELLAPI.H file in your application’s source modules and you must also link your application with the SHELL.LIB library. Both are included with the Windows 3.1 Software Development Kit.

To become a dropfile target, your application must first tell the File Manager that it can process filenames dropped into it. This is done by specifying the extended window style WS_EX_ACCEPTFILES. This style requires that you create your application’s window using the CreateWindowEx function instead of the more common CreateWindow function (see Figure 9).

Figure 9 Creating a Windows that Accepts Dropfiles

hWnd = CreateWindowEx(WS_EX_ACCEPTFILES, "ClassName", "Caption",

WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, 0L);

Alternatively, you could use the new Windows 3.1 function DragAcceptFiles.

void DragAcceptFiles(HWND hWnd, BOOL fAccept);

This function simply turns the WS_EX_ACCEPTFILES style for the specified window on or off based on the value of the fAccept parameter. If fAccept is TRUE, the bit is turned on. It is possible for an application to turn this style on or off while running. When the user is dragging a filename, the File Manager examines this style bit for the window directly under the mouse cursor. If the application does not have the WS_EX_ACCEPTFILES style on, the File Manager changes the cursor to Figure 2. This informs the user that the filename cannot be dropped at the current location. If, on the other hand, the File Manager sees that the WS_EX_ACCEPTFILES style is on, it changes the cursor to look like Figure 4.

Sometimes it is necessary for a drop-target application to call the DragAcceptFiles function. If an application is printing a file, it should call DragAcceptFiles and pass FALSE as the fAccept parameter so that the user cannot drop any filenames into the application before the printing completes. Once the document completes printing, the application should call DragAcceptFiles again with fAccept set to TRUE so that filenames can once again be dropped.

How It Works

When the user drags one or more filenames and releases the mouse button, the File Manager allocates a block of memory and fills it with the list of filenames and paths that the user selected. The File Manager then posts a WM_DROPFILES message to the window that was under the mouse cursor when the user released the mouse button. The wParam parameter of this message indicates the handle to the block of memory containing the selected file’s pathnames. The lParam parameter for this message is not used.

Once this message has been received, the application calls the DragQueryFile function to process the memory block.

WORD DragQueryFile(HDROP hDrop, WORD wFileNum, LPSTR

lpszFile, WORD wMaxFileSize);

The hDrop parameter is set to the memory handle containing the list of pathnames that have been dropped into the window. This handle was passed as the wParam parameter for the WM_DROPFILES message. The wFileNum parameter indicates which pathname should be retrieved from the memory block. This value can be between zero and the number of pathnames dropped minus one. If the value is 1, DragQueryFile returns the total number of pathnames contained in the memory block.

When DragQueryFile is used to request an individual pathname, the lpszFile parameter gives the location of a buffer where the indicated pathname should be copied. The wMaxFileSize parameter is the maximum number of bytes that can be copied into this buffer. After copying the pathname, DragQueryFile returns the number of bytes actually copied into the buffer.

It is possible to determine the length of an individual entry’s pathname by specifying NULL as the lpszFile parameter to this function. When you do this, DragQueryFile returns the length of the specified pathname.

Figure 10, a fragment from a dropfile target’s WinProc, demonstrates how all of the dropped pathnames could be added to a list box.

Figure 10 Processing Dropped Files

case WM_DROPFILES:

// Get the number of pathnames that have been dropped

wNumFilesDropped = DragQueryFile((HDROP) wParam, -1, NULL, 0);

// Get the handle to the listbox and empty it

hWndLB = GetDlgItem(hWnd, ID_LISTBOX);

SendMessage(hWndLB, LB_RESETCONTENT,0, 0);

// Add each pathname to the list box

for (x = 0 ; x < wNumFilesDropped; x++) {

// Get the number of bytes required by the file's full pathname

wPathnameSize = DragQueryFile((HDROP) wParam, x, NULL, 0);

// Allocate memory to contain full pathname & zero byte

npszFile = (NPSTR) LocalAlloc(LPTR, wPathnameSize += 1);

// If not enough memory, skip this one

if (npszFile = = NULL) continue;

// Copy the pathname into the buffer & add to listbox

DragQueryFile((HDROP) wParam, x, npszFile, wPathnameSize);

SendMessage(hWndLB, LB_ADDSTRING, 0, (LONG) (LPSTR) npszFile);

LocalFree(npszFile);

}

// Free the memory block containing the dropped-file information

DragFinish((HDROP) wParam);

break;

After the WM_DROPFILES message has been processed, the memory block containing all of the pathnames must be freed via a call to DragFinish. This function’s only parameter is the handle to the memory block containing the list of dropped pathnames. If you do not call DragFinish, the memory block will not be freed until the application that allocated the block (in this case the File Manager) is terminated. If the user does several drag-and-drop operations, extra memory is unnecessarily used.

In addition to the list of pathnames contained in the memory block, the File Manager also adds other information that may be useful to the dropfile client application. This information can be obtained by calling DragQueryPoint.

BOOL DragQueryPoint(HDROP hDrop, LPPOINT lpPoint)

As always, the hDrop parameter indicates the handle to the memory block, while the lpPoint parameter indicates the address to a POINT data structure. DragQueryPoint copies the coordinates of the mouse cursor when the files were dropped into the lpPoint buffer. The x and y values in the POINT structure pertain to the client area of the window receiving the WM_DROPFILES message. The DragQueryPoint function returns TRUE if the mouse cursor is in the client area of the window or FALSE if it is outside of the client area.

An application can use this information to determine how to process the dropped pathnames. For example, Write opens a file if it is dropped on a non-client area (such as its title bar) and inserts the file as a package into the current document if it is dropped in Write’s client area (see Figure 8).

Sometimes it does not make sense for an application to have more than one pathname dropped into it. For example, when multiple filenames are dropped onto Write’s non-client area, Write simply ignores the request to open a file. But when multiple files are dropped onto Write’s client area, Write inserts all of the files as packages into the current document. Whether your application uses the dropped pathnames or not, the block of memory must still be freed by calling DragFinish.

BurnIt

BurnIt is a tiny application I’ve written to demonstrate how to make an application a drop target (see Figure 11). It runs as an icon only. When you drop one or more files from the File Manager into BurnIt’s fireplace, BurnIt deletes them (see Figure 12).

Figure 12 BurnIt is in the topmost z-order.

BurnIt registers a window class and then creates an instance of this class in the minimized state. It does so by specifying the WS_MINIMIZE style in the call to CreateWindowEx. And since this window allows filenames to be dropped onto it, the WS_EX_ACCEPTFILES style is also specified in this call.

So that BurnIt always runs as an icon that can never be restored or maximized, the following code is added to its window procedure:

case WM_QUERYOPEN:

// Don’t allow this app to be opened. It only runs

// as an icon.

lResult = FALSE;

break;

Whenever Windows is about to restore or maximize an iconized window, it sends the WM_QUERYOPEN message to that window’s window procedure. If the window procedure responds by returning FALSE, Windows does not open the window.

When I use the File Manager, I always maximize it. The problem with this, of course, is that the File Manager completely obscures the BurnIt icon. Fortunately, Windows 3.1 offers a solution to this problem. Windows 3.1 supplies two z-orders that a window can be placed in. The new z-order is called the topmost z-order. Windows in the topmost z-order are closer to the user than windows not in the topmost z-order. Because the BurnIt window is in the topmost z-order, it always appears on top of the File Manager window, even when the File Manager is maximized (see Figure 12).

The SetWindowPos function has been enhanced in Windows 3.1 to support moving windows into and out of the topmost z-order. In the BurnIt program, the code to place the window in the topmost z-order can be found in the processing of the WM_CREATE message in the BurnItWndProc function.

case WM_CREATE:

SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,

SWP_NOMOVE | SWP_NOSIZE);

When calling SetWindowPos, the second parameter indicates which window should immediately precede your window (the first parameter) in the z-order. However, you can specify any of the identifiers in Figure 13 instead.

Figure 13 Specifying Z-order Placement

HWND_BOTTOM Places the window at the bottom of the z-order. If the window is in the topmost z-order, it loses its topmost status and becomes the bottom-most window.
HWND_TOP Places the window at the top of the z-order.
HWND_TOPMOST Places the window in the topmost z-order.
HWND_NOTOPMOST Places the window at the top of the non-topmost z-order.

The real processing for BurnIt is the handling of the WM_DROPFILES message. During this message, a loop is started that iterates through all of the pathnames contained in the memory block passed to BurnIt by the File Manager. For each entry, the full pathname is copied to a local block of memory using the DragQueryFile function; then the pathname is passed to the OpenFile function.

OpenFile(npszPathName, &of, OF_DELETE);

The call to OpenFile tells Windows to delete the file indicated by the npszPathName parameter. When the loop has cycled through every pathname, the DragFinish function is called so that the memory block is freed.

Adding drag-and-drop to your own applications can be done with only a few lines of code.

Becoming a Dropfile Server

Currently, the data structures and functions necessary for implementing a dragfile sourcer are undocumented. I decided to investigate the four Dragxxx API functions. I also investigated the memory block created by the File Manager and passed to a window procedure via the wParam parameter for the WM_DROPFILES message. I was able to do all this by tracing through the assembly-language code for each of the four API functions.

But before I explain what I found, I’d like to emphasize that it’s officially undocumented. Anything you use from this section you use at your own risk. Any of the functions’ internals or the structure of the memory block discussed here may change in future versions of Windows, possibly breaking your code.

Let’s begin with the internal data structure. This is the structure that is created by the File Manager when the user drops pathnames into a window (see Figure 14).

Figure 14 The "DROPFILESTRUCT"

typedef struct {

WORD wSize; // Number of bytes in this structure

POINT ptMousePos; // Mouse position

BOOL fInNonClientArea; // TRUE if mouse was in client area

// Pathnames begin after structure each one zero-terminated

// Zero-length pathname used to indicate the end

} DROPFILESTRUCT, FAR *LPDROPFILESTRUCT;

After allocating a memory block large enough to hold this structure, the wSize member is initialized to the number of bytes in the structure.

lpDropFileStruct->wSize = sizeof(DROPFILESTRUCT);

The ptMousePos member is set to the x and y coordinates of the mouse when it was released (these coordinates are relative to the client area of the window under the mouse cursor). The fInNonClientArea member is set to TRUE if the mouse was in the window’s non-client area when the button was released. Following this structure in the memory block is the full pathname for each of the filenames selected in the File Manager. Each pathname is terminated with a zero byte; an extra zero byte after the last pathname indicates the end of the list.

To implement dragfile-sourcing, your application must create a memory block containing the structure described above. I have written two functions to help you do this. The first function, DragInitBlock (see Figure 15), allocates a global block of memory large enough to contain the DRAGFILESTRUCT structure and initializes its three data members. The handle to this memory block is returned by the function or NULL if an error occurred.

Figure 15 DragInitBlock

HGLOBAL FAR PASCAL DragInitBlock(LPPOINT lpptMousePos, BOOL fInNonClientArea) {

HDROP hMem;

LPDROPFILESTRUCT lpDropFileStruct;

// GMEM_SHARE must be specified because the block will

// be accessed by another application

hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE | GMEM_ZEROINIT,

sizeof(DROPFILESTRUCT) + 1);

// If unsuccessful, return NULL

if (hMem = = NULL) return(hMem);

// Lock block and initialize the data members

lpDropFileStruct = (LPDROPFILESTRUCT) GlobalLock(hMem);

lpDropFileStruct->wSize = sizeof(DROPFILESTRUCT);

lpDropFileStruct->ptMousePos = *lpptMousePos;

lpDropFileStruct->fInNonClientArea = fInNonClientArea;

GlobalUnlock(hMem);

return(hMem);

}

You’ll notice that the global memory block is allocated using the GMEM_SHARE flag. If you examine this flag in WINDOWS.H, you’ll see that it has the same value as the GMEM_DDESHARE flag. This flag indicates to Windows that the application that allocated this memory block is not necessarily the same application that is going to access it. Actually, when running Windows 3.0 in protected mode, or Windows 3.1, specifying the GMEM_SHARE flag doesn’t have any effect because all applications share the same address space. However, when running Windows 3.0 in real mode with expanded memory, each application has its own address space. If the GMEM_SHARE flag was not specified when creating the memory block, the memory allocated by one application will not be visible to any other application.

The second function, DragAppendFile (see Figure 16), can be called repeatedly to append new pathnames to the memory block. The first parameter to this function is the handle to the memory block created by a call to DragInitBlock or a previous call to DragAppendFile. DragAppendFile locks the block and counts up the number of bytes that are currently being used in it. The block is enlarged (using GlobalReAlloc) so it can contain all of the block’s current data plus the new pathname being appended. If this is successful, the new pathname is copied onto the end of the block and the new global memory handle is returned.

Figure 16 DragAppendFile

HGLOBAL FAR PASCAL DragAppendFile(HDROP hMem, LPCSTR szPathname) {

LPDROPFILESTRUCT lpDropFileStruct;

LPCSTR lpCrnt;

WORD wSize;

lpDropFileStruct = (LPDROPFILESTRUCT) GlobalLock(hMem);

// Point to first pathname in list

lpCrnt = (LPSTR) lpDropFileStruct + lpDropFileStruct->wSize;

// Search for a pathname were first byte is a zero byte

while (*lpCrnt) { // While the 1st char of path is non-zero

while (*lpCrnt) lpCrnt++; // Skip to zero byte

lpCrnt++;

}

// Calculate current size of block

wSize = (WORD) (lpCrnt - (LPSTR) lpDropFileStruct + 1);

GlobalUnlock(hMem);

// Increase block size to accommodate new pathname being appended

hMem = GlobalReAlloc(hMem, wSize + lstrlen(szPathname) + 1,

GMEM_MOVEABLE | GMEM_ZEROINIT | GMEM_SHARE);

// Return NULL if insufficient memory

if (hMem = = NULL) return(hMem);

lpDropFileStruct = (LPDROPFILESTRUCT) GlobalLock(hMem);

// Append the pathname to the block

lstrcpy((LPSTR) lpDropFileStruct + wSize - 1, szPathname);

GlobalUnlock(hMem);

return(hMem); // Return the new handle to the block

}

After all of the pathnames are appended to the block with DragAppendFile, the window under the mouse cursor can process the dropped pathnames. This is done by having the dropfile server post a WM_DROPFILES message to the dropfile client, passing the memory handle returned from DragAppendFile as the wParam parameter. It is important to use PostMessage instead of SendMessage here so that Windows does not enter a deadlock situation. To understand this, let’s examine the following situation.

When filenames are dropped onto a Program Manager group, the Program Manager adds icons to the group window. If a file is dropped that does not represent an executable program or whose extension has no association, the Program Manager displays a message box (see Figure 17).

Figure 17 Program Manager can't add this to a group.

For the Program Manager to become the active task (so that it can present the message box), Windows must make the dragfile sourcer yield control. Windows does this by posting messages into the dragfile sourcer’s queue. If the dragfile sourcer sends the WM_DROPFILES message by calling SendMessage instead of PostMessage, it will be unable to process any messages that have accumulated in its queue. This puts Windows in a deadlock situation--it must take control away from the active task but that task is unable to give it up because it is waiting for SendMessage to return.

There is a little-known Windows API function called ReplyMessage. This function exists solely to avoid deadlock situations. When an application calls the ReplyMessage function, Windows returns immediately to the application that called the SendMessage function. When that application completes its processing and returns back to its GetMessage loop, Windows activates the application that was sent the original message and allows that application to continue processing the code following the call to ReplyMessage.

While calling the ReplyMessage function would allow you to send the WM_DROPFILES message with SendMessage instead of PostMessage, the call to ReplyMessage has to be placed in the dropfile target application. Since you probably didn’t write this application, you can’t modify its code and you must transmit the WM_DROPFILES message using PostMessage.

Figure 18 demonstrates how to drop two files (XYZ.TXT and ABC.WRI) onto the Print Manager. It is assumed that the Print Manager is running.

Figure 18 Dropping Two Files on the Print Manager

HGLOBAL hMem, hMemT;

// Create the initial block telling the Print Manager that the

// mouse was clicked in its left-top corner of its client area

hMem = DragInitBlock(&MAKEPOINT(0), FALSE);

if (hMem = = NULL) break;

// Append the two files to the memory block

hMemT = DragAppendFile(hMem, "C:\\WINDOWS\\XYZ.TXT");

if (hMemT != NULL) hMem = hMemT;

hMemT = DragAppendFile(hMem, "C:\\WINDOWS\\ABC.WRI");

if (hMemT != NULL) hMem = hMemT;

PostMessage(FindWindow("PrintManager", NULL), WM_DROPFILES, hMem, 0);

// When the message returns, do NOT free the block. It is freed by

// the window receiving the WM_DROPFILES message.

Sample Dropfile Server

Figure 19 contains the listings for a sample dropfile server I’ve written called (you guessed it!) Dropfile. When you run it, you first choose the filename(s) you wish to drag with the Select files! menu pick. This option causes a File Open dialog box to appear. This is one of the common dialogs from the Windows 3.1 COMMDLG.DLL file. Unlike most File Open dialog boxes, I allow the user to select multiple files (see Figure 20). This is done by specifying the OFN_ALLOWMULTISELECT flag when initializing the Flags member of the OPENFILENAME structure.

Figure 20 Dropfile.

When the user presses the OK button in this dialog, the GetOpenFileName function fills the szAllFilenames buffer with a string having the form:

Path File1 File2 File3 ...\0

For example, if I select the Calculator, Notepad, and Recorder filenames from the WIN31 directory on drive C:, the szAllFilenames buffer would look like this:

C:\WIN31 CALC.EXE NOTEPAD.EXE RECORDER.EXE\0

Once this string is returned, the number of filenames in the string can be determined by counting the number of spaces. This is done by sending the user-defined message FW_GETNUMFILENAMES to the dragfile sourcer’s window procedure. The number of filenames that have been selected for dropping is displayed in the application’s caption bar.

You initiate dropping pathnames by holding down the left mouse button in the client area of Dropfile’s window. When this happens, Windows sends a WM_LBUTTONDOWN message to its WinProc. This is where all the code for the dragging and dropping process is. The first step is to track the mouse position and change the shape of the cursor as it passes over different windows on the screen. The window under the mouse is determined by placing a call to the WindowFromPoint function. If this function returns the handle to a valid window, you must then check to see if the WS_EX_ACCEPTFILES style bit is turned on for that window. If the window is invalid or the WS_EX_ACCEPTFILES style is off, the cursor should look like Figure 2. If the window is valid and the WS_EX_ACCEPTFILES style is on, you determine the shape of the cursor by how many filenames have been selected to be dropped. If one filename was selected, the cursor is changed to look like Figure 4; for more than one, the cursor is changed to look like Figure 6. To save myself the trouble of drawing these cursors, I used Borland’s Resource Workshop to extract the "dropping not allowed" cursor from USER.EXE and the single-file and multiple-file cursors from the File Manager.

The do/while loop causes the mouse cursor to change repeatedly until the user releases the mouse button. Passing VK_LBUTTON to GetAsyncKeyState tests for mouse button release.

When the mouse button is released, the dropfile memory block is created, initialized, and the full pathname for each of the files that had originally been selected by the user is appended to it.

For initialization, you simply convert the mouse position from screen coordinates to client coordinates and then determine if the mouse cursor was in the client area of the window or not. The first task is done by simply calling the ScreenToClient function.

The second is accomplished by sending the WM_NCHITTEST message to the dropfile client window. When the window procedure receives this message, it examines the coordinates, which are passed in the lParam parameter, and determines the part of the window at this location. For example, the return value indicates HTCAPTION if the coordinate is somewhere in the window’s caption or HTSYSMENU if the coordinate is in the area occupied by the window’s system menu. For this application, you just need to see if the coordinate is in the client area of the window or its non-client area. The return value from sending the WM_NCHITTEST message will be HTCLIENT if the coordinate is in the window’s client area. Any other return value indicates a coordinate in the window’s non-client area.

Armed with the initialization information, you can call the DragInitBlock function to allocate the dropfile memory block and initialize it. Assuming that all went well (a valid memory handle was returned), you can loop through all of the filenames selected by the user, construct a full pathname for each of them, and append each one to the memory block by calling the DragAppendFile function. After all of the pathnames have been appended, the WM_DROPFILES message (with the handle to the dropfile memory block as the wParam parameter) can be posted (not sent) to the dropfile target application. Remember, the dragfile sourcer mustn’t free this memory because it’ll be freed by the dropfile target when it’s finished with it.

Where Do We Go From Here?

The drag-and-drop technology implemented in Windows 3.1 is both simple and incredibly useful. It’s something that almost every Windows-based application should add to its feature list. As you can see, there is no "magic" code written by some guru who only surfaces to eat pizza breakfasts. Of course, one obstacle hindering the acceptance of drag-and-drop is that there is no official drag-sourcing specification.

Windows drag-and-drop is still in its infancy and could use some tuning and adjustment. One enhancement might be the ability for a potential drop-target window to notify the File Manager if multiple filenames can be dropped on it, or a way to specify if files can be dropped in the target application’s client area, non-client area, or both. It would also be nice if there was a way for a drop-target application to specify the shape of the mouse cursor as the mouse is moved over it. With this enhancement, the application could indicate to the user the type of action that would be performed as a file is dragged over various locations.

While there is still more work to be done to Windows drag-and-drop, it is a technology that will change and hopefully improve how users work with Windows.

1For ease of reading, "Windows" refers to the Microsoft Windows operating system. "Windows" is a trademark that refers only to this Microsoft product.