Contents
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's
IDataObject::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::CreateViewObject
will 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.
typedef struct tagFORMATETC
{
CLIPFORMAT cfFormat; DVTARGETDEVICE *ptd;
DWORD dwAspect;
LONG lindex;
DWORD tymed;
}FORMATETC, *LPFORMATETC;
typedef struct tagSTGMEDIUM
{
DWORD tymed; union {
HBITMAP hBitmap;
HMETAFILEPICT hMetaFilePict;
HENHMETAFILE hEnhMetaFile;
HGLOBAL hGlobal; LPWSTR lpszFileName;
IStream *pstm;
IStorage *pstg;
};
IUnknown *pUnkForRelease;
}STGMEDIUM;
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.
typedef struct _DROPFILES {
DWORD pFiles;
POINT pt;
BOOL fNC;
BOOL fWide;
} DROPFILES, FAR * LPDROPFILES;
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.
Drop effects include:
DROPEFFECT_NONE
: Drop target cannot accept the data. Corresponding cursor is 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 "linked to" 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:
class CNWSModule : public CComModule
{
public:
HRESULT Init(_ATL_OBJMAP_ENTRY* p, HINSTANCE h, const GUID* plibid = NULL)
{
::OleInitialize(NULL);
....
<a name="RegCF_src"></a>m_CFSTR_NSEDRAGDROP =
(CLIPFORMAT)::RegisterClipboardFormat(_T("NSEDRAGDROP"));
return CComModule::Init(p, h, plibid);
}
void Term()
{
....
::OleUninitialize();
CComModule::Term();
}
......
CLIPFORMAT m_CFSTR_NSEDRAGDROP;
};
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 data members
CMyVirtualFolder *m_pFolder; UINT m_iAcceptFmt; LPITEMIDLIST m_pidl; CNWSPidlMgr m_PidlMgr;
Self-defined functions
HRESULT _Init(CMyVirtualFolder *pFolder, LPCITEMIDLIST pidl);
DWORD _QueryDrop();
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
. - Otherwise, drop effect is
DROPEFFECT_NONE
.
STDMETHOD(DragEnter)(LPDATAOBJECT pDataObj,
DWORD dwKeyState,
POINTL,
LPDWORD pdwEffect)
{
ATLASSERT(pDataObj);
m_iAcceptFmt = 0;
FORMATETC fe2 = { _Module.m_CFSTR_NSEDRAGDROP, NULL,
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
if( S_OK == pDataObj->QueryGetData(&fe2) )
m_iAcceptFmt = FMT_NSEDRAGDROP_INDEX;
else
{
FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
if( S_OK == pDataObj->QueryGetData(&fe) )
m_iAcceptFmt = FMT_HDROP_INDEX;
}
*pdwEffect = _QueryDrop();
return S_OK;
}
For DragEnter()
, we had decided on the drop effect, here we just need to reuse the result.
STDMETHOD(DragOver)(DWORD dwKeyState, POINTL , LPDWORD pdwEffect)
{
*pdwEffect = _QueryDrop();
return S_OK;
}
When the cursor leaves, we reset the m_iAcceptFmt
to 0
.
STDMETHOD(DragLeave)(VOID)
{
m_iAcceptFmt = 0;
return S_OK;
}
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.
We call IShellFolder::_DoDrop
to accomplish the task. Please refer to Handle the dropped data object to see how we realize it.
STDMETHOD(Drop)(LPDATAOBJECT pDataObj,
DWORD dwKeyState,
POINTL ,
LPDWORD pdwEffect)
{
ATLASSERT(pDataObj);
ATLASSERT(pdwEffect);
ATLASSERT(m_pFolder);
*pdwEffect = DROPEFFECT_NONE;
DWORD dwDropEffect = _QueryDrop();
if( dwDropEffect == DROPEFFECT_NONE )
return S_OK;
HRESULT Hr;
if( SUCCEEDED(Hr = m_pFolder->_DoDrop(pDataObj, dwDropEffect, m_pidl,
m_iAcceptFmt) ) )
{
*pdwEffect = dwDropEffect;
return S_OK;
}
else
return Hr;
}
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
.
QueryContinueDrag
: Determines whether a drag-and-drop operation should continue. GiveFeedback
: Gives visual feedback to an end user during a drag-and-drop operation.
The logic to determine where the drag-and-drop operation should continue or not in our implementation is simple:
- If the Esc key has been pressed since the previous call, the operation is cancelled by the user without dropping the data.
- If the left button of the mouse is up, the operation is completed with data being dropped.
- Otherwise, the drag operation will be continued.
STDMETHOD(QueryContinueDrag)(BOOL bEsc, DWORD dwKeyState)
{
if( bEsc )
return ResultFromScode(DRAGDROP_S_CANCEL);
if( (dwKeyState & MK_LBUTTON)==0 )
return ResultFromScode(DRAGDROP_S_DROP);
return S_OK;
}
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.
STDMETHOD(GiveFeedback)(DWORD)
{
return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
}
IDataObject
IDataObject
is a data object's primary interface. It must be implemented by all data objects and used by both source and target for a variety of purposes, including:
- Loading data into the data object.
- Extracting data from the data object.
- Determining what types of data are in the data object.
- Providing feedback to the data object on the outcome of the data transfer.
The data object dragged from our NSE only supports two kinds of data described in following FORMATETC
structures:
- Format 1:
{ _Module.m_CFSTR_NSEDRAGDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }
: Which will be used when transfering data inside our NSE. - Format 2:
{ CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }
: Used when transfering data from our NSE to Explorer.
Following data will be extracted from our data object according to the specified format:
- Format 1: The complex
pidl
of the dragged file. - Format 2: The absolute path of the file created in temporary folder with the same filename as the dragged file that can be packed into
DROPFILES
struct.
Self-defined data members
CMyVirtualFolder* m_pFolder; LPITEMIDLIST m_pidl; CNWSPidlMgr m_PidlMgr;
Self-defined functions
HRESULT _Init(CMyVirtualFolder *pFolder, LPCITEMIDLIST pidl);
HRESULT _GetLMouseStatus(BOOL *bM_LButtonUp);
Support this type of data or not?
Determines whether the data object is capable of rendering the data described in the FORMATETC
structure.
STDMETHOD(QueryGetData)(FORMATETC* pFormatetc)
{
ATLASSERT(pFormatetc);
if(pFormatetc == NULL)
return E_INVALIDARG;
if( pFormatetc->cfFormat != _Module.m_CFSTR_NSEDRAGDROP &&
pFormatetc->cfFormat != CF_HDROP )
return DV_E_FORMATETC;
if(pFormatetc->ptd!=NULL)
return E_INVALIDARG;
if( (pFormatetc->dwAspect & DVASPECT_CONTENT)==0 )
return DV_E_DVASPECT;
if(pFormatetc->lindex !=-1)
return DV_E_LINDEX;
if((pFormatetc->tymed & TYMED_HGLOBAL) ==0)
return DV_E_TYMED;
return S_OK;
}
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.
STDMETHOD(GetData)(FORMATETC *pFormatetc, STGMEDIUM *pMedium)
{
ATLASSERT(m_pFolder);
if(m_pFolder==NULL)
return E_FAIL;
VALIDATE_POINTER(pFormatetc);
VALIDATE_POINTER(pMedium);
HRESULT Hr;
::ZeroMemory(pMedium, sizeof(STGMEDIUM));
if( pFormatetc->cfFormat == _Module.m_CFSTR_NSEDRAGDROP )
{
UINT dwSize = m_PidlMgr.GetByteSize(m_pidl);
HGLOBAL hMem = ::GlobalAlloc(GMEM_ZEROINIT| GMEM_MOVEABLE
| GMEM_SHARE | GMEM_DISCARDABLE, dwSize);
if( hMem==NULL )
return E_OUTOFMEMORY;
LPTSTR psz = (LPTSTR)::GlobalLock(hMem);
ATLASSERT(psz);
if ( NULL == psz )
{
::GlobalFree ( hMem );
return E_UNEXPECTED;
}
::CopyMemory(psz,m_pidl,dwSize);
::GlobalUnlock(hMem);
pMedium->tymed = TYMED_HGLOBAL;
pMedium->hGlobal = hMem;
pMedium->pUnkForRelease = NULL;
return S_OK;
}
else if( pFormatetc->cfFormat == CF_HDROP ) {
BOOL bM_LButtonUp = FALSE;
HR(_GetLMouseStatus(&bM_LButtonUp));
TCHAR szTmpFileName[MAX_PATH]=_T("");
if( bM_LButtonUp == TRUE )
{
DWORD dwSize=MAX_PATH;
TCHAR szFileName[MAX_PATH]=_T("");
LPITEMIDLIST pidlTemp = m_PidlMgr.GetLastItem(m_pidl);
HR(m_PidlMgr.GetName(pidlTemp,szFileName));
if(dwSize == 0)
return E_FAIL;
dwSize=MAX_PATH;
::GetTempPath(dwSize,szTmpFileName);
_tcscat(szTmpFileName,szFileName);
FILE* fpTmp = _tfopen(szTmpFileName,_T("a"));
if(fpTmp == NULL)
return E_FAIL;
fclose(fpTmp);
}
HGLOBAL hgDrop = ::GlobalAlloc ( GPTR, sizeof(DROPFILES) +
sizeof(TCHAR) * (_tcslen(szTmpFileName)+1) );
if ( NULL == hgDrop )
return E_OUTOFMEMORY;
DROPFILES* pDrop = (DROPFILES*) ::GlobalLock ( hgDrop );
ATLASSERT(pDrop);
if ( NULL == pDrop )
{
::GlobalFree ( hgDrop );
return E_UNEXPECTED;
}
pDrop->pFiles = sizeof(DROPFILES);
#ifdef _UNICODE
pDrop->fWide = TRUE;
#endif
LPTSTR pszBuff = (LPTSTR) (LPBYTE(pDrop) + sizeof(DROPFILES));
_tcscpy( pszBuff, (LPCTSTR) szTmpFileName );
::GlobalUnlock ( hgDrop );
pMedium->tymed = TYMED_HGLOBAL;
pMedium->hGlobal = hgDrop;
pMedium->pUnkForRelease = NULL;
return S_OK;
}
return DV_E_FORMATETC;
}
Enumerate all supported clipboard formats
Creates an object for enumerating the FORMATETC
structures for our data object.
STDMETHOD(EnumFormatEtc)(DWORD ,
IEnumFORMATETC** ppenumFormatEtc)
{
VALIDATE_POINTER(ppenumFormatEtc);
HRESULT Hr;
CComObject<CNSFEnumFmtEtc> *obj;
HR( CComObject<CNSFEnumFmtEtc>::CreateInstance(&obj) );
obj->AddRef();
Hr=obj->QueryInterface(IID_IEnumFORMATETC, (LPVOID *)ppenumFormatEtc);
obj->Release();
return Hr;
}
IEnumFORMATETC
The IEnumFORMATETC
interface is used to enumerate an array of FORMATETC
structures. IEnumFORMATETC
has the same methods as all enumerator interfaces: Next
, Skip
, Reset
, and Clone
.
The order of formats enumerated through the IEnumFORMATETC
object should be the same as the order that the formats would be in when placed on the clipboard.
As we known, here we only support two formats.
STDMETHOD(Next)(ULONG, LPFORMATETC pFormatetc, ULONG *pdwCopied)
{
VALIDATE_POINTER(pFormatetc);
if( pdwCopied!=NULL )
*pdwCopied = 1L;
m_dwEnumPos++;
switch( m_dwEnumPos )
{
case 2:
pFormatetc->cfFormat = CF_HDROP;
pFormatetc->ptd = NULL;
pFormatetc->dwAspect = DVASPECT_CONTENT;
pFormatetc->lindex = -1;
pFormatetc->tymed = TYMED_HGLOBAL;
break;
case 1:
pFormatetc->cfFormat = _Module.m_CFSTR_NSEDRAGDROP;
pFormatetc->ptd = NULL;
pFormatetc->dwAspect = DVASPECT_CONTENT;
pFormatetc->lindex = -1;
pFormatetc->tymed = TYMED_HGLOBAL;
break;
default:
if( pdwCopied!=NULL )
*pdwCopied = 0L;
return S_FALSE;
}
return S_OK;
}
STDMETHOD(Skip)(ULONG n)
{
m_dwEnumPos += n;
return S_OK;
}
STDMETHOD(Reset)(void)
{
m_dwEnumPos = 0L;
return S_OK;
}
IShellFolder
Support drop on Tree View's subfolder
STDMETHODIMP CMyVirtualFolder::GetUIObjectOf(HWND hWnd,
UINT nCount,
LPCITEMIDLIST* pidls,
REFIID riid, LPUINT,
LPVOID* ppRetVal)
{
......
if( riid == IID_IDropTarget )
{
if( nCount !=1)
return E_FAIL;
CComObject<CNSFDropTarget>* pDropTarget;
HR( CComObject<CNSFDropTarget>::CreateInstance(&pDropTarget) );
pDropTarget->AddRef();
HR( pDropTarget->_Init(this, *pidls) );
Hr=pDropTarget->QueryInterface(IID_IDropTarget, ppRetVal);
pDropTarget->Release();
return Hr;
}
return E_NOINTERFACE;
}
Support drop on Tree View's root folder and IShellView
STDMETHODIMP CMyVirtualFolder::CreateViewObject(HWND hWnd,
REFIID riid,
LPVOID* ppRetVal)
{
VALIDATE_OUT_POINTER(ppRetVal);
HRESULT Hr;
......
if( riid == IID_IDropTarget )
{
CComObject<CNSFDropTarget> *pDropTarget;
HR( CComObject<CNSFDropTarget>::CreateInstance(&pDropTarget) );
pDropTarget->AddRef();
HR( pDropTarget->_Init(this, NULL) );
Hr=pDropTarget->QueryInterface(IID_IDropTarget, ppRetVal);
pDropTarget->Release();
return Hr;
}
return E_NOINTERFACE;
}
Set correct attributes to subfolder object
STDMETHODIMP CMyVirtualFolder::GetAttributesOf(UINT uCount,
LPCITEMIDLIST pidls[],
LPDWORD pdwAttribs)
{
*pdwAttribs = (DWORD)-1;
if(( uCount==0 )||(pidls[0]->mkid.cb == 0))
{
DWORD dwAttr;
dwAttr |= SFGAO_FOLDER | SFGAO_HASSUBFOLDER
|SFGAO_HASPROPSHEET |SFGAO_DROPTARGET;
*pdwAttribs &= dwAttr;
}
else
{
for( UINT i=0; i<uCount; i++ )
{
DWORD dwAttr = 0;
switch( m_PidlMgr.GetItemType(pidls[i]) )
{
case NWS_FOLDER:
.....
dwAttr |=SFGAO_DROPTARGET;
......
break;
}
*pdwAttribs &= dwAttr;
}
}
return S_OK;
}
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.
HRESULT CMyVirtualFolder::_DoDrop(LPDATAOBJECT pDataObj,
DWORD dwDropEffect,
LPITEMIDLIST pidlDropDest,
UINT iFmtIdx)
{
VALIDATE_POINTER(pDataObj);
HRESULT Hr = E_INVALIDARG;
STGMEDIUM stgmed;
if(FMT_HDROP_INDEX == iFmtIdx)
{
FORMATETC fe1 = { CF_HDROP, NULL, DVASPECT_CONTENT, -1,
TYMED_HGLOBAL };
if( SUCCEEDED( pDataObj->GetData(&fe1, &stgmed) ) )
{
Hr = _DoDrop_HDROP(stgmed.hGlobal, dwDropEffect,pidlDropDest);
::ReleaseStgMedium(&stgmed);
return Hr;
}
}
else if( FMT_NSEDRAGDROP_INDEX == iFmtIdx )
{
FORMATETC fe2 = { _Module.m_CFSTR_NSEDRAGDROP, NULL,
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
if( SUCCEEDED( pDataObj->GetData(&fe2, &stgmed) ) )
{
Hr = _DoDrop_NSEDRAGDROP
(stgmed.hGlobal, dwDropEffect,pidlDropDest);
::ReleaseStgMedium(&stgmed);
return Hr;
}
}
return Hr;
}
HRESULT CMyVirtualFolder::_DoDrop_HDROP(HGLOBAL hMem,
DWORD dwDropEffect,
LPITEMIDLIST pidlDropDest)
{
VALIDATE_POINTER(hMem);
TCHAR szMsg[MAX_PATH]={0};
HRESULT Hr = S_OK;;
LPITEMIDLIST pidlComplexDest = m_PidlMgr.Concatenate
(m_pidlPath,pidlDropDest);
TCHAR szDropDest[MAX_PATH]=_T("");
DWORD dwSize=MAX_PATH;
if(pidlComplexDest) {
HR(m_PidlMgr.GetFullName(pidlComplexDest,szDropDest,&dwSize));
if(dwSize == 0)
{
m_PidlMgr.Delete(pidlComplexDest);
return E_FAIL;
}
}
DWORD dwFileOpFlags = 0;
HDROP hDrop = (HDROP) ::GlobalLock(hMem);
if( hDrop==NULL )
{
m_PidlMgr.Delete(pidlComplexDest);
return E_OUTOFMEMORY;
}
TCHAR szItemName[MAX_PATH]={0};
PTCHAR pChr = NULL;
TCHAR szFileName[MAX_PATH]={0};
UINT nFiles = ::DragQueryFile(hDrop, (UINT) -1, NULL, 0);
for( UINT i=0; i<nFiles; i++ )
{
if( ::DragQueryFile(hDrop, i, szFileName, MAX_PATH)==0 )
{
m_PidlMgr.Delete(pidlComplexDest);
::GlobalUnlock(hMem);
return E_FAIL;
}
if((::GetFileAttributes(szFileName) & FILE_ATTRIBUTE_DIRECTORY)!=0)
{
MessageBox(NULL,_T("Drag & Drop operation be stopped!
\nOur NSE doesn't support drag&drop operation on folder object,
\nPlease make sure to drag file objects!"),
_T("NSExtDragDrop"),MB_OK);
m_PidlMgr.Delete(pidlComplexDest);
::GlobalUnlock(hMem);
return E_FAIL;
}
}
for( i=0; i<nFiles; i++ )
{
pChr = _tcsrchr(szFileName,_T('\\'));
_tcscpy(szItemName,pChr+1);
AddItemToCfgFile(pidlComplexDest,szItemName,NWS_FILE);
RefreshShellViewWndsExcept( NULL);
}
m_PidlMgr.Delete(pidlComplexDest);
::GlobalUnlock(hMem);
return Hr;
}
HRESULT CMyVirtualFolder::_DoDrop_NSEDRAGDROP
(HGLOBAL hMem, DWORD dwDropEffect,LPITEMIDLIST pidlDropDest)
{
HRESULT Hr;
TCHAR szDropDest[MAX_PATH]=_T("");
DWORD dwSize=MAX_PATH;
LPITEMIDLIST pidlComplexDest = m_PidlMgr.Concatenate
(m_pidlPath,pidlDropDest);
if(pidlComplexDest) {
HR(m_PidlMgr.GetFullName(pidlComplexDest,szDropDest,&dwSize));
if(dwSize == 0)
{
m_PidlMgr.Delete(pidlComplexDest);
return E_FAIL;
}
}
LPITEMIDLIST pidlDragObject = (LPITEMIDLIST) ::GlobalLock(hMem);
if( pidlDragObject == NULL )
{
m_PidlMgr.Delete(pidlComplexDest);
return E_FAIL;
}
TCHAR szDragObject[MAX_PATH]=_T("");
dwSize=MAX_PATH;
HR(m_PidlMgr.GetFullName(pidlDragObject,szDragObject,&dwSize));
if( _DragDropInSamePath(szDragObject,szDropDest))
{
m_PidlMgr.Delete(pidlComplexDest);
::GlobalUnlock(hMem);
return E_FAIL;
}
_tcsnset(szDragObject,0,MAX_PATH);
LPITEMIDLIST pidlTemp = m_PidlMgr.GetLastItem(pidlDragObject);
HR(m_PidlMgr.GetName(pidlTemp,szDragObject));
ITEM_TYPE iItemType = m_PidlMgr.GetItemType(pidlDragObject);
if(AddItemToCfgFile(pidlComplexDest,szDragObject,iItemType)==FALSE)
{
MessageBox(NULL,_T
("Destination Folder had include a same name file."),
_T("Name conflict"),MB_OK);
m_PidlMgr.Delete(pidlComplexDest);
::GlobalUnlock(hMem);
return E_FAIL;
}
m_PidlMgr.Delete(pidlComplexDest);
::GlobalUnlock(hMem);
return Hr;
}
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.
LPDROPTARGET m_pDropTarget; HWND m_hwndList;
And then create the drop target object and register it with listview after the view Window is created in IShellView::CreateViewWindow.
STDMETHODIMP CNSFShellView::CreateViewWindow(
LPSHELLVIEW lpPrevView,
LPCFOLDERSETTINGS lpFS,
LPSHELLBROWSER pSB,
LPRECT prcView,
HWND* phWnd)
{
......
HR( m_pFolder->CreateViewObject(m_hWnd, IID_IDropTarget,
(LPVOID*) &m_pDropTarget) );
ATLASSERT(m_pDropTarget);
HR( ::RegisterDragDrop(m_hwndList, m_pDropTarget) );
return S_OK;
}
Of course, we should unregister it before the view Window will be destroyed.
STDMETHODIMP CNSFShellView::DestroyViewWindow(void)
{
::RevokeDragDrop(m_hwndList);
m_pDropTarget->Release();
UIActivate(SVUIA_DEACTIVATE);
::DestroyWindow(m_hWnd);
m_pShellBrowser->Release();
return S_OK;
}
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
- Call the
DoDragDrop
- Delete the dragged file from current listview.
LRESULT CNSFShellView::OnBeginDrag(UINT ClitID, LPNMHDR lpnmh, BOOL& bHandled)
{
LPITEMIDLIST pidlSelected=NULL;
UINT nCount = ListView_GetSelectedCount(m_hwndList);
if( 0 == nCount)
{
pidlSelected = NULL;
return E_FAIL;
}
else
{
LV_ITEM lvItem;
int nSelItem = ListView_GetNextItem( m_hwndList, -1, LVIS_SELECTED );
ZeroMemory(&lvItem, sizeof(lvItem));
lvItem.mask = LVIF_PARAM;
lvItem.iItem = nSelItem;
if(ListView_GetItem(m_hwndList, &lvItem))
{
pidlSelected=(LPITEMIDLIST)(lvItem.lParam);
if(NWS_FOLDER == m_PidlMgr.GetItemType(pidlSelected))
return E_FAIL;
}
}
CComObject<CNSFDATAOBJECT>* pDataObject;
HR( CComObject<CNSFDATAOBJECT>::CreateInstance(&pDataObject) );
pDataObject->AddRef();
LPITEMIDLIST pidlComplex;
pidlComplex = m_PidlMgr.Concatenate(m_pFolder->m_pidlPath,pidlSelected);
HR( pDataObject->_Init(m_pFolder, pidlComplex) );
if( SUCCEEDED(Hr) && (pDataObject!=NULL) )
{
CComObject<CNSFDROPSOURCE>* pDropSource;
HR( CComObject<CNSFDROPSOURCE>::CreateInstance(&pDropSource) );
pDropSource->AddRef();
DWORD dwEffect = 0;
Hr = ::DoDragDrop(pDataObject,pDropSource,DROPEFFECT_MOVE,&dwEffect);
pDropSource->Release();
if(Hr)
{
DeleteItemInCfgFile(pidlComplex);
Refresh();
RefreshShellViewWndsExcept(m_hWnd);
}
}
pDataObject->Release();
m_PidlMgr.Delete(pidlComplex);
return S_OK;
}
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.
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.