Introduction

I've seen several questions around the CodeProject boards lately asking about doing drag and drop between a program and Explorer windows. Like many things in Windows, it seems easy once you know how it's done, but finding the answer can be quite a chore. In this article, I demonstrate how to hook up drag and drop so your program can accept drops from an Explorer window, and be a drag source so your users can drag files into an Explorer window.

The sample project is an MFC app, and the article assumes you're familiar with C++, MFC, and using COM objects and interfaces. If you need help with COM objects and interfaces, check out my Intro to COM article. The program is MultiFiler, a little utility that acts like a drag and drop "staging area". You can drag any number of files into MultiFiler, and it shows them all in a list. You can then drag files back out to Explorer windows, using the Shift or Ctrl keys to tell Explorer to move or copy the original files, respectively.

Drag and Drop with Explorer

As you know, Explorer lets you drag files among Explorer windows and the desktop. When you begin a drag operation, the Explorer window you drag from (the drop source), creates a COM object implementing the IDataObject interface, and puts some data into the object. The window you drag into (the drop target), then reads that data using IDataObject methods; that's how it knows what files are being dropped.

If you check out the data contained in the IDataObject with a viewer like ClipSpy, you'll see that Explorer puts several data formats in the data object:

The important format is CF_HDROP. The other formats are custom formats registered by Explorer for its own use. If we write an app that registers its window as a drop target, and if we know how to read CF_HDROP data, we'll be able to accept dropped files. Similarly, if we can fill a data object with CF_HDROP data, Explorer will let our app be a drag source. So, what's contained in that CF_HDROP format? Read on...

The DROPFILES data structure

So what exactly is the CF_HDROP format? It turns out that it's just a DROPFILESstruct. There's also the HDROP type, which is simply a pointer to a DROPFILESstruct.

The one thing that isn't listed in the struct definition is the list of filenames. The list is formatted as a double-null terminated list of strings. But where is it stored? It is actually stored right after the fWide member, and pFiles holds the offset (relative to the beginning of the struct) where the list is located in memory. The only other member that's used in drag and drop is fWide, which indicates whether the filenames are in ANSI or Unicode characters.

Accepting a drag and drop from Explorer

Accepting a drag and drop is much easier than initiating one, so I'll cover accepting first.

There are two ways for your window to accept drag and drop. The first way is a holdover from Windows 3.1 and uses the WM_DROPFILES message. The other way is to register your window as an OLE drop target.

The old way - WM_DROPFILES

To use the old method, you first set the "accept files" style in your window. For dialogs, this is on the "Extended Styles" page, as shown here:

If you want to set this style at runtime, call the DragAcceptFiles() API, which takes two parameters. The first is your main window handle, and the second is TRUE to indicate you can accept drag and drop. If your main window is a CView instead of a dialog, you'll need to set this style at runtime.

No matter which of the two methods you use, your window becomes a drop target. When you drag files or folders from an Explorer window and drop them in your window, the window receives a WM_DROPFILES message. The WPARAM of a WM_DROPFILES message is an HDROP that lists what files are being dropped. There are three APIs you use to get the file list out of the HDROP: DragQueryFile(), DragQueryPoint(), and DragFinish().

DragQueryFile() does two things: returns the number of files being dragged, and enumerates through the list of files. DragQueryPoint() returns the pt member of the DROPFILESstruct. DragFinish() frees up memory allocated during the drag and drop process.

DragQueryFile() takes four parameters: The HDROP, the index of the filename to return, a buffer allocated by the caller to hold the name, and the size of the buffer in characters. If you pass -1 as the index, DragQueryFile() returns the number of files in the list. Otherwise, it returns the number of characters in the filename. You can test this return against 0 to tell if the call succeeded.

DragQueryPoint() takes two parameters, the HDROP and a pointer to a POINTstruct that receives the value in the pt member of the DROPFILESstruct. DragFinish() just takes one parameter, the HDROP.

DragQueryPoint() isn't necessary if all you want is the list of files. (Actually, I've never had to use it myself.)

The new way - using an OLE drop target

The other method of accepting drag and drop is to register your window as an OLE drop target. Normally, doing so would require that you write a C++ class that implements the IDropTarget interface. However, MFC has a COleDropTarget class that takes care of that for us. The process is a bit different depending on whether your main window is a dialog or a CView, so I'll cover both below.

Making a CView a drop target

CView already has some drag and drop support built-in, however it's not normally activated. To activate it, you add a COleDropTarget member variable to the view, and then call its Register() function in your view's OnInitialUpdate() to make the view a drop target, as shown below:

OnDragEnter()

pDataObject: Pointer to a COleDataObject that contains the data being dragged.

dwKeyState: A set of flags indicating which mouse button is clicked and which shift keys (if any) are pressed. The flags are MK_CONTROL, MK_SHIFT, MK_ALT, MK_LBUTTON, MK_MBUTTON, and MK_RBUTTON.

point: The cursor position, expressed in the view's client coordinates.

OnDragEnter() returns a DROPEFFECT value, which tells OLE whether the drop will be accepted, and if so, what cursor should be displayed. The values and their meanings are:

DROPEFFECT_NONE: The drop will not be accepted. The cursor changes to: .

DROPEFFECT_MOVE: The data will be moved by the drop target. The cursor changes to: .

DROPEFFECT_COPY: The data will be copied by the drop target. The cursor changes to:

DROPEFFECT_LINK: The data will be linked to by the drop target. The cursor changes to: .

Normally, in OnDragEnter() you examine the data being dragged and see if it meets your criteria. If not, you return DROPEFFECT_NONE to reject the drag and drop. Otherwise, you can return one of the other values depending on what you intend to do with the data.

OnDragOver()

If you return a value other than DROPEFFECT_NONE from OnDragEnter(), OnDragOver() is called whenever the mouse cursor moves within your window. The prototype of OnDragOver() is:

The parameters and return value are identical to OnDragEnter(). OnDragOver() lets you return different DROPEFFECT values depending on the cursor position and the shift key state. For instance, if your main view window has several areas, displaying different lists of information, and you want to allow drops only in one part, you'd check the cursor position in the point parameter, and return DROPEFFECT_NONE if the cursor is not in that area.

As for the shift keys, you normally react to them as described below:

SHIFT pressed (MK_SHIFT in dwKeyState): Return DROPEFFECT_MOVE.

CONTROL pressed (MK_CONTROL): Return DROPEFFECT_COPY.

Both pressed (MK_SHIFT | MK_CONTROL): Return DROPEFFECT_LINK.

These are only guidelines, although it's best to adhere to them, since they are what Explorer uses. But if some of the actions (copy, move, or link) doesn't make sense for your app, you don't have to return the corresponding DROPEFFECT. For instance, in MultiFiler (I'll get to it, I promise!) OnDragOver() always returns DROPEFFECT_COPY. Just be sure to return the right value, so that the cursor accurately indicates to the user, what will happen if he drops in your window.

OnDragLeave()

OnDragLeave() is called if the user drags out of your window without dropping. The prototype is:

void CView::OnDragLeave();

It has no parameters or return value - its purpose is to let you clean up any memory you allocated during OnDragEnter() and OnDragOver().

OnDrop()

If the user drops over your window (and you didn't return DROPEFFECT_NONE from the most recent call to OnDragOver()), then OnDrop() is called so you can act on the drag and drop. The prototype of OnDrop() is:

The dropEffect parameter is equal to the last return value from OnDragOver(), and the others are the same as OnDragEnter(). The return value is TRUE if the drop is completed successfully (it's up to you to define what a "successful" completion is), or FALSE if not.

OnDrop() is where all the action happens - you can act on the dropped data in whatever way makes sense for your app. In MultiFiler, the dropped files are added to the main window's list control.

Making a dialog a drop target

Things are a bit more difficult if your main window is a dialog (or anything not derived from CView). Since the base COleDropTarget implementation is designed to work only with a CView-derived window, you need to derive a new class from COleDropTarget and override the four methods outlined above.

In this example, the constructor is passed a pointer to the main window, so the drop target methods can send messages and do other stuff in the dialog. You can change this to whatever best suits your needs. You then implement the four drag and drop methods as described in the previous section. The only difference is the additional CWnd* parameter, which is a pointer to the window that the cursor is over at the time of the call.

Once you have this new class, you add a drop target member variable to your dialog and call its Register() function in OnInitDialog():

BOOL CMyDialog::OnInitDialog()
{
// Register our dialog as a drop target.// m_droptarget is a CMyDropTarget member of CMyDialog.
m_droptarget.Register ( this );
}

Accessing the HDROP data in a CDataObject

If you use an OLE drop target, your drag and drop functions receive a pointer to a COleDataObject. This is an MFC class that implements IDataObject and contains all of the data that the drag source created when the drag began. You'll need a bit of code to look for CF_HDROP data in the data object and get an HDROP handle. Once you have an HDROP, you can use DragQueryFile(), as shown earlier, to read the list of dropped files.

Summary of the two methods

Holdover from Windows 3.1; it could conceivably be removed in the future.

No customization of the drag and drop process possible - you can only act after the drop occurs.

Doesn't let you inspect the raw data being dropped.

If you don't need any fancy customization, this method is much easier to code.

Using an OLE drop target:

Uses a COM interface, which is a modern and better-supported mechanism.

Good MFC support through CView and COleDropTarget.

Allows full control over the drag and drop operation.

Gives you access to the raw IDataObject so you can access any data format.

Requires a bit more code, but once you write it once, you can cut and paste it to new projects.

How MultiFiler accepts drag and drop

The demo project ZIP file actually contains three MultiFiler projects. Each uses one of the techniques I've described to accept drag and drop from Explorer windows. When the user drops over the MultiFiler window, all of the dropped files are added to a list control, as shown here:

MultiFiler automatically eliminates duplicate files, so any file will only appear once in the list.

If you are using Windows 2000 and run a MultiFiler that uses an OLE drop target, you'll notice that the drag image is the new-fangled faded style:

This doesn't come for free, but it's a nice example of the customization you can do when you use an OLE drop target. I'll explain how to do this at the end of the article.

Initiating a drag and drop

To have Explorer accept dragged files, all we have to do is create some CF_HDROP data and put it in a data object. Of course, if it were that simple, I wouldn't have anything to write about. The DROPFILESstruct is a bit tricky to create (since it's not always the same size), but again, after you write the code once (or, after I write it once!) you can reuse it everywhere.

MultiFiler initiates a drag and drop when you select files in the list control and drag them. The control sends an LVN_BEGINDRAG notification message when this happens, so that's when MultiFiler creates the data object and hands it off to OLE to begin the drag and drop operation.

The steps in creating a DROPFILES are:

Enumerate all of the selected items in the list control, putting them in a string list.

Keep track of the length of each string as it's added to the string list.

Allocate memory for the DROPFILES itself and the list of filenames.

Fill in the DROPFILES members.

Copy the list of filenames into the allocated memory.

I'll go over the MultiFiler code now, so you can see exactly how to set up a DROPFILES. If you feel like checking out the source in the MultiFiler projects, all three do it the same way, so you can look at any of the three.

The first step is to put all of the selected filenames in a list, and keep track of the memory needed to hold all of the strings.

At this point, uBuffSize holds the total length of all the strings, including nulls, in characters. We add 1 for the final null to terminate the list, then multiply that by sizeof(TCHAR) to convert from characters to bytes. We then add sizeof(DROPFILES) to get the final required buffer size.

Note that the pFiles member doesn't indicate the size of the DROPFILESstruct; it's the offset of the file list. But since the file list is located right after the end of the struct, its offset is the same as the size of the struct.

Now we can copy all of the filenames into memory, and then unlock the buffer.

Now, this would be sufficient to initiate a drag and drop, but there's one more detail to take care of. Since MultiFiler accepts drag and drop, it will happily accept the drag and drop we are about to initiate ourselves. While that's not disastrous, it's not very neat either. So we will add another bit of data to the data source, in a custom clipboard format that we register. Our OnDragEnter() and OnDragOver() functions will check for this format, and if it's present, they will not accept the drop.

Note that we don't have to set the data to any particular value - the fact that the data is in the data source is the important part.

Now that we've put together the data, we can start the drag and drop operation! We call the DoDragDrop() method of COleDataSource, which does not return until the drag and drop is completed. The only parameter is one or more DROPEFFECT values that indicate what operations we will allow the user to do. It returns a DROPEFFECT value indicating what the user wants to do with the data, or DROPEFFECT_NONE if the drop was aborted or not accepted by the target.

In our case, we only allow copying and moving. During the drag and drop, the user can hold down the Control or Shift key to change the operation. For some reason, passing DROPEFFECT_LINK does not make Explorer create shortcuts, so that's why I didn't include DROPEFFECT_LINK in the call above.

Once DoDragDrop() returns, we check the return value. If it's DROPEFFECT_MOVE or DROPEFFECT_COPY, the drag and drop completed successfully, so we remove all of the selected files from the main window's list control. If it's DROPEFFECT_NONE, things are a bit tricky. On Windows 9x, it means the operation was canceled. However, on NT/2000, the shell returns DROPEFFECT_NONE for move operations as well. (This is by design! See KB article Q182219 for details.) So on NT, we have to manually check whether the files were moved, and if so, we remove them from the list control. The code is a bit long, so I won't repeat it here. Check out the MultiFiler source if you're interested in how it works.

The last thing we do is free the allocated memory if the drag and drop was canceled. If it completed, then the drop target owns the memory, and we must not free it. Below is the code to check the return value from DoDragDrop(), just without the NT/2000 code I just mentioned.

Other details

You can also right-click the MultiFiler list control to get a context menu with four commands. They are pretty simple, and deal with managing the selection in the list and clearing the list.

Oh, and I promised to explain how to get the cool drag image on Windows 2000! It's actually pretty simple. There is a new coclass supported by the shell called CLSID_DragDropHelper with two interfaces, IDragSourceHelper and IDropTargetHelper. IDropTargetHelper is the one that draws the drag image. It has four methods whose names should be familiar: DragEnter(), DragOver(), DragLeave(), and Drop(). All you have to do is do your normal drag and drop processing, determine what DROPEFFECT you will return, and then call the IDropTargetHelper method that corresponds to the COleDropTarget method. The IDropTargetHelper methods need the DROPEFFECT to properly draw the drag image, so that's why you need to determine it first.

If you look at the MultiFiler sample that uses a CView, you'll see two member variables:

IDropTargetHelper* m_piDropHelper;
bool m_bUseDnDHelper;

In the view's constructor, the code creates the drop helper COM object and gets an IDropTargetHelper interface. Based on whether this succeeds, m_bUseDnDHelper is set so that other functions will know whether the COM object is available.

By the way, if you don't have a recent Platform SDK installed, you may not have the definition of the IDropTargetHelper interface and the associated GUIDs. I've included the necessary definitions in each of the MultiFiler samples; just uncomment them and you should be good to go.

If you're wondering about going the other way - using IDragSourceHelper to draw the neat drag image when MultiFiler is the drag source - the documentation is less clear on this topic, and it certainly looks harder than using IDropTargetHelper, so I haven't worked on it yet.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Share

About the Author

Michael lives in sunny Mountain View, California. He started programming with an Apple //e in 4th grade, graduated from UCLA with a math degree in 1994, and immediately landed a job as a QA engineer at Symantec, working on the Norton AntiVirus team. He pretty much taught himself Windows and MFC programming, and in 1999 he designed and coded a new interface for Norton AntiVirus 2000.
Mike has been a a developer at Napster and at his own lil' startup, Zabersoft, a development company he co-founded with offices in Los Angeles and Odense, Denmark. Mike is now a senior engineer at VMware.

He also enjoys his hobbies of playing pinball, bike riding, photography, and Domion on Friday nights (current favorite combo: Village + double Pirate Ship). He would get his own snooker table too if they weren't so darn big! He is also sad that he's forgotten the languages he's studied: French, Mandarin Chinese, and Japanese.

Comments and Discussions

I hope that you are still on this forum. Your example is a very good one, and I appreciate you having it!!

I did have one question though about using this in an MDI Application. I would like to be able to make the Parent Window space a drop target, but I'm having trouble doing it. Your example assumes that there is a document already in place, and therefore, a View is already in existence.

But what about the case where I have no documents open or instantiated, and I simply want to drop a file into the Parent Window. Is there a way to do this without using the CView?

One change that would have been good for me is that rather than using the GetGlobalData() you instead had used GetData() to demonstrate using the STGMEDIUM struct to check that the data was available and accessing the hGlobal member of the STGMEDIUM struct in order to GlobalLock() and GlobalUnlock() the dragged data object.

Then a comment could say for other data types, the same sequence is used but with different CLIPFORMAT codes depending on whether you wanted an HDROP for a file drop or char pointer for text, etc.

When I handle WM_DROPFILES, would it be safe to cast HDROP to HGLOBAL?
Has anyone done it successfully across Win2k-Win7?
I would use GlobalFlags/GlobalSize to copy it as-is for later reuse (eg. repost to Clipboard, etc.). In shell extension (IShellExtInit::Initialize) it's easy because it gives me DATAOBJECT directly, and in simple standalone app, handling WM_DROPFILES is much easier than implementing full Ole drop.

As others have noted, running this app on Vista, drag and drop won't work, even if you run the app using 'Run as Administrator'. Oddly, it works just fine if you run it from inside the VC compiler (which must be 'Run as Administrator').

Hi, Thanks a lot for your detailed article on handling Drag Drop between explorer and my application. Could you help in handling the same with the MS office applications.

I have an application developed in MFC and wrote a plugin to support it on MS Office Applications(lets take MS Word). So to embed my application in MS Word the user need to go through "Tools->Insert Object" and select my application. This works perfectly fine.

Now I want to provide Drag & Drop support for the same. My Application supports the files with extension ".csc". So, when the user drags any *.csc file and drops on open instance of MS Word, then my application should be embedded in MS Word and open the dragged file.

If I change the screen resolution (for example from 1024 by 768 pixels to 1152 by 864 pixels), with the application opened, the drop event are started.
Someone have some idea why this happen?
Thanks.
Rui

First, - although it was not new to me - tnx for the very instructive and well written article.
Did you think about serving transfers via clipboard, too? I am having problems to cut a file system object in may application, and have it pasted later in Explorer. There are such things like "Delete on Paste" and "Optimized Move" like explained in MSDN:
msdn2.microsoft.com/en-us/library/aa969396.aspx">http://msdn2.microsoft.com/en-us/library/aa969396.aspx

Unfortunately not.
The article does not even mention the clipboard. It *does* talk about transferring data from/to virtual places, which is not what I want to do. I cannot apply the techniques to my problem.

tnx, I know this. I can transfer data via copy/paste through the clipboard.

AGAIN, THIS IS NOT THE QUESTION.

The question is how to handle the cut/paste scenario properly. If you think a little bit about it you will find that when you do a cut you cannot delete the object immediately. Instead you have to wait until someone pastes it later. THEN - and only then - you update your GUI on the sender site, and you show that the object is now gone. But if nobody pastes the object and instead uses the clipboard otherwise - well, then the object stays where it was. (MS calls this "delete on paste").

So, the question is: how does the sender site know when a paste has happened?

I am wondering the same thing. I know it's possible because Windows Explorer detects when the paste side of a cut/paste operation occurs.

I was thinking that maybe the program could monitor on idle the clipboard contents while still the owner of the clipboard, and when CFSTR_PASTESUCCEEDED is available in the COleDatasource to determine if DROPEFFECT_MOVE was specified. Or perhaps there could be some message sent back to the CWnd or from the COleDatasource when it receives CFSTR_PASTESUCCEEDED (this could be done by implementing a custom COleDatasource and storing a pointer to the CWnd that called in instance of the custom COleDatasource into existance and put it on the clipboard).

Also, on WM_DESTROYCLIPBOARD, which should occur if someone copies new data to the clipboard instead of ever pasting the cut data, the 'cut' state on the item list probably would need be cleared.

Of course, if the CWnd is closed then the clipboard should also be cleared (not sure if it's cleared automatically) because otherwise the paste may occur, but we can't respond to it's success, and therefore can't delete anything ourselves (in my case I have a database-backed file list).

I haven't implemented this part yet, and monitoring the clipboard in idle seems a little chunky. But I'd love to hear feedback and further ideas!

>This never appears. I came to the conclusion that you have to set up a file system watcher to determine whether the object has moved away.

Strange, it appears for me. Are you using a custom OleDataSource? If so, you have to implement the IDataObject so that SetData caches the information properly. See the 'Solution' section of this article for code:

Did you set CFSTR_PREFERREDDROPEFFECT to DROPEFFECT_MOVE? I noticed that Explorer only sets CFSTR_PASTESUCCEEDED if CFSTR_PREFERREDDROPEFFECT is set to DROPEFFECT_MOVE. If you set it to anything else (or not set at all) then Explorer only sets CFSTR_PERFORMEDDROPEFFECT and CFSTR_LOGICALPERFORMEDDROPEFFECT on paste. If you do set it to DROPEFFECT_MOVE, then Explorer sets all three on paste (and also tries to delete your DataObject out of the clipboard).

Hello, Mr. Michael. I have read through your article. It's a good article. As you say in the article, the file's name is added into the CDataSource. If I have some data need to be written into a file, and the name of the file is listed in listctrl. I hope the progrm can transfer the file's name and the data to Explorer, so it can generate a such file. Where should the data be added? I hope I can get help from you. Thank you!

hello Michael,I read this Article ,and I couldn't find any way to get Explorer current path before drop files. I try to use the Mouse Hook,and find the SHELLDLL_View and FolderView,but i can't find the path!
Could you help me ?

I noticed the same thing. It looks like there's only a problem if you're debugging the application in Visual Studio. If you run the application yourself then it works OK. I haven't found a workaround for this yet.

I am using your code to drag files from explorer to an MDI application.
When the file is in drag process the source windows explorer is blocked. Why?
How can I avoid the explorer to be blocked?
Thanks,
Rui

The only thing you have access to is the DROPFILES struct that you receive when the drop happens, so you're at the mercy of whatever order the files are in inside the <big><small>DROPFILES</small></big>.

Hi, the program Im makin accepts drops from different locations, etc...
Now I have written the following code to get the total no of drops i made, and extract their locations from the list.
Is the code correct, if not, can anyone suggest any other method, please??

well, i managed to make the app work, my app does a lil' more than what it has to, doesnt matter
My question, i have all the files in my list.....I have four destinations entered through a 4 browse buttons i made to work, which simply means i have 4 destinations....i also have some way to copy these files into the destination....
The only problem is, how do i extract the filed details, of all the files that is, their locations, etc, so that i can initialize the copy thing??....
please reply asap...

the registerclipboardformat....how do you use it, in the app you gave, its used in a weird way, how do i use it in my application, my project is called projectcomplete...coud you please help me out?>...please!!