Introduction

To implement drag and drop (abbr. D&D) operation between Shell Namespace Extension (abbr. NSE) and Explorer is a little bit more complicated than implementing it between common Windows-based applications and Explorer. As we known, NSE includes two parts in UI, Tree View and Shell View. To implement D&D in Shell View is similar in common applications, but as for Tree View, you need some other skills. To support D&D operation in NSE involves implementing shell interfaces including IDataObject, IDropTarget, IDropSource, IEnumFORMATETC and you should carefully handle those interfaces while implement IShellFolder and IShellView.

Another thing I must point out is --- don't mix D&D up with another shell technology drag-and-drop handler. The biggest difference in UI between the two things is that D&D operation normally happens with the left mouse button, while drag-and-drop handler is a shortcut menu handler called when a file is dragged with the right mouse button.

Before reading this article, I recommend you read my first article about NSE Tips in Writing Namespace Extension (I) - Implements Subfolder -- which introduces how our sample NSE's data be organized in detail (I mean to make all my articles about NSE based on the same kind of NSE data). This article assumes you know C++, ATL, COM and are especially familiar with basic knowledge of NSE.

The sample project is created using ATL COM AppWizard. Your can freely drag and drop files between Explorer and sample NSE or just within sample NSE. For simplicity, we don't support a folder as the dragged data. However, the key elements about how to implement drag and drop are the same.

About Drag and Drop

The D&D operation involves four participants to take care of the whole process:

Drag source: Implemented by the object where the data will be dragged from, which should expose an IDropSource interface.

Data object: Implemented by the object that contains the data being dragged, which should expose an IDataObject interface.

Drop target: Implemented by the object that is intended to accept the drop, which should expose an IDropTarget interface.

DoDragDrop: The function implemented by OLE and used to initiate a drag and drop operation. Once the operation is in progress, it facilitates communication between the drag source and the drop target.

For example, if you drag a file from Explorer to our NSE, the Explorer is the drag source and is responsible for preparing the data object and initiating the DoDragDrop function, NSE is the drop target and is responsible for handling the dropped data object.

What happened during drag and drop

The following procedure outlines the essential steps that are typically used to transfer data with drag and drop (quote from MSDN):

The target calls RegisterDragDrop to give the system a pointer to its IDropTarget interface and register a window as a drop target.

When the user starts a drag-and-drop operation, the source creates a data object and initiates a "drag loop" by calling DoDragDrop.

When the cursor is over the target window, the system notifies the target by calling one of the target's IDropTarget methods. The system calls IDropTarget::DragEnter when the cursor enters the target window, and IDropTarget::DragOver as the cursor passes over the target window. Both methods provide the drop target with the current cursor position and the state of the keyboard modifier keys such as CTRL or ALT. When the cursor leaves the target window, the system notifies the target by calling IDropTarget::DragLeave. When any of these methods return, the system calls the IDropSource interface to pass the return value to the source.

When the user releases the mouse button to drop the data, the system calls the target's IDropTarget::Drop method. Among the method's parameters is a pointer to the data object's IDataObject interface.

The target calls the data object'sIDataObject::GetData method to extract the data.

With some Shell data transfers, the target might also need to call the data object's IDataObject::SetData method to provide feedback to the source on the outcome of the data transfer.

When the target is finished with the data object, it returns from IDropTarget::Drop. The system returns the source's DoDragDrop call to notify the source that the data transfer is complete.

Depending on the particular data transfer scenario, the source might need to take additional action based on the value returned by DoDragDrop and the values that are passed to the data object by the target. For instance, when a file is moved, the source must check these values to determine whether it must delete the original file.

The source releases the data object.

Implement drop in Tree View and Shell View

When the user drops an object into NSE's Tree View:

If the target is NSE's subfolder, Explorer will call IShellFolder::GetUIObjectOf method with the riid parameter set to IID_IDropTarget to create a drop target object and get the IDropTarget interface pointer back to handle the drag-and-drop operation.

If the target is NSE's root folder, IShellFolder::CreateViewObjectwill be called by Explorer instead of IShellFodler::GetUIObjectOf to require an IDropTraget interface pointer.

Meanwhile, you should set the corresponding attribute to subfolder by add SFGAO_DROPTARGET attribute to folder object when Explorer calls IShellFolder::GetAttributesOf to retrieve the subfolder's feature. As for the root folder, you should add SFGAO_DROPTARGET attribute to "Attributes" value in "ShellFolder" key when registering the NSE.

To support drop on Shell View, you should:

Register the IDropTarget interface pointer of target object with the Shell View's window which will accept dropped data by RegisterDragDrop.

Implement drag in Tree View and Shell View

When the user drags an object from NSE's Tree View:

Explorer will call IShellFolder::GetUIObjectOf method and set the riid parameter to IID_IDataObject to create a data object and get the IDataObject interface pointer to handle the drag-and-drop operation.

To support dragging an object from NSE's Shell View:

When handling the LVN_BEGINDRAG notify triggered by drag-and-drop operation, you should create a data object which implements the IDataObject interface together with a freshly created source object which supports the IDropSource interface, and use those two interface pointers to initiate drag-and-drop operation by calling DoDragDrop function.

Data object: data to be transferred during Drag and Drop

As we known, data object is the object holding the data that is dragged from the drag source to the drop target. What data will be held by the data object is totally decided by your application's requirement. But there are still some rules about how the data will be organized.

Which kind of data that data object will hold is identified by the clipboard formats data object supported. And each clipboard format has a corresponding structure which specifies the way in which dragged data will be organized in its storage medium. There are some system registered clipboard formats and users can also register their own private clipboard formats for their applications. For system clipboard formats, the system has predefined the corresponding structure. As for the application's private clipboard format, it is the application designer who is responsible for defining how data can be organized.

Normally, the data object will support several clipboard formats simultaneously.

The FORMATETC structure is used by methods in the D&D to specifying which kind of data can be requested or be interested in, besides clipboard format. It also includes other information about the data such as type of storage medium, and target device to use, etc.

The STGMEDIUM structure is used to specify the data's real storage medium.

The shell itself always uses the tymed of TYMED_HGLOBAL together with the dwAspect set to DVASPECT_CONTENT, which means it uses the global memory as the dragged data's storage medium.

Clipboard Formats and Drop Effects

CF_HDROP

The most commonly used system clipboard format in D&D is CF_HDROP.

The data correspond to CF_HDROP consists of an STGMEDIUM structure which contains a global memory object. The structure's hGlobal member points to a DROPFILES structure as its hGlobal member. And the pFiles member of the DROPFILES structure contains an offset to a double NULL-terminated character array containing the dragged files' names.

When dragging files from Explorer, Explorer will prepare a data object which supports CF_HDROP clipboard format. Explorer will also try to acquire the data object by using the CF_HDROP format to retrieve the data when Explorer receives a data object.

So, as long as the drop target and data object are implemented in our NSE support CF_HDROP format, our NSE will accept the data dragged from Explorer and Explorer will accept the data dragged from our NSE.

The question is: since in our NSE, the file objects are all virtual files (i.e. there is no real file object mapped with the virtual file object), which filename will be used when preparing the DROPFILES for the CF_HDROP format? Our solution is to create a temporary file with the same name of virtual file, and use this temp file's information to fill the DROPFILES structure.

Register your own Clipboard Format for NSE

Another situation is that sometimes D&D may happened inside our NSE. It is easy to image, if we can find out a way enable us to use the internal data structure to store data in storage medium when D&D is inside NSE instead of something like DROPFILES, then when we handle the data object, the process will become more convenient because we spare ourselves the steps of packing the data into DROPFILES and then unpacking back.

The key point is find out a way to distinguish the data object dragged from inside NSE from those dragged from outside. The solution is to register a private clipboard format and use the internal data structure to organize the data in a storage medium. When our NSE receives a data object, the first thing we do is to query the data object whether it supports the private clipboard format or not? If yes, then the data object is dragged within NSE, we can draw the data which stores use internal structure out and handles it directly.

I choose to declare the private clipboard format m_CFSTR_NSEDRAGDROP as the data member of _Module, a global instance of CComModule which is automatically generated by ATL COM AppWizard when creating our NSE. For the source code of declare and register the clipboard format please refer here.

And the data stored in storage medium corresponding to the private clipboard format is the complex pidl of the dragged file object.

Choose the appropriate Drop Effect

Drop effect defines the effects of a drag-and-drop operation. The System uses the drop effect to decide which cursor should be displayed when dragging over the target and how we handle the original data when D&D completes successfully.

DROPEFFECT_COPY: Drop results in a copy. The original data is untouched by the drag source. Corresponding cursor is

DROPEFFECT_MOVE: Drag source should remove the data. Corresponding cursor is

DROPEFFECT_LINK: The data will be "linkedto" by the drop target. Corresponding cursor is

Drop effect will be returned by drop target methods to tell the system to use the correct cursor and notify the source of how to treat the original data. It will also be used by the drag source to specify which kind of drop effect will be supported in D&D operation.

In sample NSE, when D&D completes successfully:

If the data is dragged from our NSE, either it drop inside NSE or into Explorer, we choose the effect: DROPEFFECT_MOVE, i.e., the original file to be dragged will be deleted.

If the data is dragged from Explorer and dropped into our NSE, we choose the effect: DROPEFFECT_COPY, for we don't want to make changes to the original file.

Certainly, you can choose the drop effect suitable for your own application.

Initialize the COM Library

Applications that use the drag and drop functionality must call OleInitialize before calling any other function in the COM library.

So, we should call ::OleInitialize when we init the COM module, and ::OleUninitialize when the COM module will be terminated. Like this:

Implement Shell Interfaces

Note:The standard methods of interfaces which have not appeared in following description are treated as not implemented.

IDropTarget

If you want to develop a NSE without a feature of Tree View (that is, NSE without subfolder) and don't want the root of NSE to be the drop target, you can simply let the Shell View implement IDropTarget itself. But if you take the Tree view into consideration, when you want to support drop operation in root folder and subfolders, which will also require an object to expose an IDropTarget interface, an independent class CNSFDropTarget to implement IDropTarget interface is a better choice.

The advantage is that you can reuse the class in both situations. Furthermore, the source code becomes easier to maintain. You will see that all other interfaces (except IShellFolder and IShellView) relative to drag-and-drop implementation are be realized in independent classes for the same reason.

Self-defined functions

HRESULT _Init(CMyVirtualFolder *pFolder, LPCITEMIDLIST pidl);
// initialize data members
DWORD _QueryDrop();//decide which kind of drop effect we should take
//according to the accepted clipboard format

DragEnter/DragOver/DragLeave/Drop

DragEnter(): Called when the cursor enters your Window.

DragOver(): Called when the cursor moves inside your Window.

DragLeave(): Called when the cursor leaves your Window.

Drop(): Called when the user drops in your Window.

Explorer calls the DragEnter method to make sure whether a drop can be accepted by the drop target, and, if so, the effect of the drop.

When implementing this method, we will call data object's QueryGetData function twice to ask whether the data object supports the clipboard format we cared. Like this:

First, does it support _Module.m_CFSTR_NSEDRAGDROP? If so, drop effect set to DROPEFFECT_MOVE.

Else, does it support CF_HDROP? If so, drop effect is DROPEFFECT_COPY.

Drop() is where the data object will finally be handled, you can deal with the dropped data object in whatever way makes sense for your app. In our NSE, the dropped files are added to the Shell View and filenames will been added to the target folder's corresponding section in configuration file.

IDropSource

Drag source must create an object that exposes an IDropSource interface. This interface allows the source to update the drag image that indicates the current position of the cursor and to provide feedback to the system on how to terminate a drag-and-drop operation. IDropSource has two methods: GiveFeedback and QueryContinueDrag.

While in the drag loop, a drop source is responsible for keeping track of the cursor position and displaying an appropriate drag image. Here, we let OLE to update the cursor for us, using its defaults.

Extract data from our data object

This function will be called by a data consumer to obtain data from a source data object. It renders the data described in the specified FORMATETC structure and transfers it through the specified STGMEDIUM structure.

Please refer here to see which FORMATETC structure our NSE supports and what data will be filled in the specified STGMEDIUM structure.

Handle the dropped data object

In our NSE, the dropped files are added to the Shell View and filenames will been added to the target folder's corresponding section in configuration file.

If the data object is dragged from Explorer, we retrieve the filename of each dragged file from data object and add it into current listview in our Shell View.

If the data object is dragged from our NSE, we retrieve the filename of each dragged file from data object and add it into current listview in our Shell View and delete the dragged file object from our NSE.

IShellView

Enable Shell View to be the Drop Target

To make Shell View be a drop target, all we need do is call RegisterDragDrop function to register the listview Window as the target of an OLE drag-and-drop operation and specify the IDropTarget instance to use for drop operations.

The IDropTarget instance can be retrieved through call IShellFolder::CreateViewObject to request an object which implemented IDropTarget.

First, we should declare a IDropTarget interface pointer as a data member of view object.

Drag from Shell View

Shell View not only can be the drop target but can also act as a data source for an OLE drag-and-drop operation. The solution is to call DoDragDrop when you detect that the user has started a drag-and-drop operation, that is, when handling LVN_BEGINDRAG notification.

The DoDragDrop function enters a loop in which it calls various methods in the IDropSource and IDropTarget interfaces.

Before calling DoDragDrop function, you should prepare two objects: the data object that contains the data being dragged and drop source object which will be used to communicate with the source during the drag operation.

Which can described in following steps:

Find the item to be dragged

Prepare the source object and the data object according to the dragged item

Conclusion

Hoo, all my articles about NSE finally finished. Writing articles is more challenging for me than writing code. However, you will find it will really help you to have a clearer insight into the topic and sometimes even make you aware of some subtle design deficiency.

I always see complaints about how boring NSE is . Here I share what I learned during develop my own NSE, and hope these will be of help to you.

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

ZengXi is a SOHO guy. Her expertise includes ATL, COM, Web Service, XML, Database Systems and Information Security Technology. Now she is interested in Instant Messages softwares. She also enjoys her hobbies of reading books, listening music and watching cartoons.

I am writing a program to drag file or picture from IE.
when a picture has a url at it,i set the type as DROPEFFECT_LINK,
but i don't know how get the url data.
and can i get url and filename at the same time when i drap it?
thanks very much.

If you use ClipSpy (pls refer to http://www.codeproject.com/clipboard/clipspy.asp)to look into the data object be dragged from IE, you will see the url/filename info is stored in clipboard format:UniformResourceLocator. You can fetch the url info by query the dropped file/picture by this clipboard format.

I learned it from clipspy,thanks.
but there is a problem:
I'm writing a program which drag image form IE,
but when that image has a url on it,i only get the "UniformResourceLocator"
format,and I can't get the hdrop fromat so the image can't be dragged.
Can u give me some suggestions?

hellow, I have asked you about the question how to implement a custom MyDocument to map a real folder.But in NSE, you customize shellview,other than MyDocument's system view! I want to know how to use system view to show virtual folder's content from a real folder!!!

In fact, I have no idea of how to change My Documents and make it map to specified real folder other than current one. My Documents is a NSE created by system, in my opinion, which real folder it will map is decided inside.

Shell provide NSE as a machanism to extend the system's namespace. You can use Shell Functions to get information about shell object such as file and folder or virtual folder. While as to context menu in system namespace, please refer to http://www.codeproject.com/shell/TipsInNSE_SubFld.asp?df=100&forumid=223189&select=1274563#xx1274563xx

I started to write about NSE's, but once I found out how much work was involved, I totally lost interest. Thanks for taking up the slack Once I get around to updating my NSE article, I'll point people to this series, which is far superior. 非常感谢