Click here to Skip to main content
Click here to Skip to main content

Tips in Writing Namespace Extension (III) - Drag and Drop objects between System Namespace and your NSE

, 22 Feb 2006
Rate this:
Please Sign up or sign in to vote.
An article on implementing drag and drop operation between your NSE and system namespace.

Sample Image - NSExtDragDrop.jpg

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):

  1. The target calls RegisterDragDrop to give the system a pointer to its IDropTarget interface and register a window as a drop target.
  2. When the user starts a drag-and-drop operation, the source creates a data object and initiates a "drag loop" by calling DoDragDrop.
  3. 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.
  4. 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.
  5. The target calls the data object's IDataObject::GetData method to extract the data.
  6. 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.
  7. 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.
  8. 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.
  9. 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; //Particular clipboard format of interest
    DVTARGETDEVICE  *ptd;
    DWORD           dwAspect; 
                    //Constants that indicate how much detail should be 
                    //contained in the rendering
    LONG            lindex;
    DWORD           tymed;    
                    //Type of storage medium used to transfer 
                    //the object's data
}FORMATETC, *LPFORMATETC;

typedef struct tagSTGMEDIUM
{
    DWORD tymed;             //Type of storage medium
    union {
        HBITMAP        hBitmap;
        HMETAFILEPICT  hMetaFilePict;
        HENHMETAFILE   hEnhMetaFile;
        HGLOBAL        hGlobal;    //Global memory handle. 
                        //Address of the data be transferred.
        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);

        .... //initialize other data members

        // Register Self-defined clipformat
        <a name="RegCF_src"></a>m_CFSTR_NSEDRAGDROP = 
        (CLIPFORMAT)::RegisterClipboardFormat(_T("NSEDRAGDROP"));

        return CComModule::Init(p, h, plibid);
    }
    void  Term()
    {
        .... //release the resource occupied by data members

        ::OleUninitialize();

        CComModule::Term();
    }

    ...... //other data members

    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; //current folder object
UINT m_iAcceptFmt;        //format used to handle the dropped data object
LPITEMIDLIST m_pidl;      //!= NULL: simple pidl of the folder which accepts
                    // drop object (relative to current folder)
            //== NULL: indicate that data object dropped 
                    //in current folder
CNWSPidlMgr  m_PidlMgr; //pidl management object

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.
  • Otherwise, drop effect is DROPEFFECT_NONE.
STDMETHOD(DragEnter)(LPDATAOBJECT pDataObj,
                            DWORD dwKeyState,
                            POINTL,
                            LPDWORD pdwEffect)
{
    ATLASSERT(pDataObj);
    m_iAcceptFmt = 0;

    // Does the data object support private clipboard format?
    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 /*pt*/, 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 /*pt*/,
                      LPDWORD pdwEffect)
{
    ATLASSERT(pDataObj);
    ATLASSERT(pdwEffect);
    ATLASSERT(m_pFolder);

    *pdwEffect = DROPEFFECT_NONE; // default to failed/cancelled

    // Determine drop effect...
    DWORD dwDropEffect = _QueryDrop();
    if( dwDropEffect == DROPEFFECT_NONE )
        return S_OK;

    // Drop data...
    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;   //folder which data object dragged from
LPITEMIDLIST m_pidl;           //the complex pidl of selected file
CNWSPidlMgr m_PidlMgr;         //pidl management object

Self-defined functions

HRESULT _Init(CMyVirtualFolder *pFolder, LPCITEMIDLIST pidl); 
        //initialize the data members
HRESULT _GetLMouseStatus(BOOL *bM_LButtonUp);  
        //Used to get the status of left button mouse
        //which indicates whether the drag-and-drop loop ended or not.

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 ) 
                    //Drag-and-Drop inside NSE
    {
        //Transfer the PIDL
        UINT dwSize = m_PidlMgr.GetByteSize(m_pidl); 
                    //m_pidl is a complex 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 ) //drag from NSE to system NS
    {
        //Transfer the temporary create file name

        BOOL bM_LButtonUp = FALSE; 
            //status of mouse left button (FALSE:down TRUE:up)
        HR(_GetLMouseStatus(&bM_LButtonUp));

        TCHAR szTmpFileName[MAX_PATH]=_T("");

        //1 create a temporary file use the selected object's name
        if( bM_LButtonUp == TRUE )
        {
            DWORD dwSize=MAX_PATH;

            //1.1 get selected object's name
            TCHAR szFileName[MAX_PATH]=_T("");
            LPITEMIDLIST  pidlTemp = m_PidlMgr.GetLastItem(m_pidl);
            HR(m_PidlMgr.GetName(pidlTemp,szFileName));
            if(dwSize == 0)
                return E_FAIL;

            //1.2 create a temporary file use the name
            dwSize=MAX_PATH;
            ::GetTempPath(dwSize,szTmpFileName);
            _tcscat(szTmpFileName,szFileName);

            FILE* fpTmp = _tfopen(szTmpFileName,_T("a"));
            if(fpTmp == NULL)
                return E_FAIL;
            fclose(fpTmp);
        }

        //Alloc memory for data: DropFile Struct + FileName string
        //the data that follows is a double null-terminated list 
        //of file names.
        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;
        }

        // Fill in the DROPFILES struct.
        pDrop->pFiles = sizeof(DROPFILES);
#ifdef _UNICODE
        // If we're compiling for Unicode, 
        // set the Unicode flag in the struct to
        // indicate it contains Unicode strings.
        pDrop->fWide = TRUE;
#endif
        // Append FileName data after DROPFILES Structure
        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 /*dwDirection*/, 
                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)
{
    ...... // support other interfaces like IContextMenu IExtractIcon etc

    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;

    ...... // support other interface like IShellView etc

    //1.Called by the IShellView to enable view object be the drop target
    //2.If you want the root folder be the droptarget, 
    // you should support IDropTarget in this method
    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))
    {
        // Can happen on Win95. Queries the view folder.
        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; 
                //enable the folder be the drop target
                ......
                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)
    {
        // Check for HDROP
        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 )
    {
        // Check for CFSTR_NEFILENAME
        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;;

    // Get target path
    LPITEMIDLIST pidlComplexDest = m_PidlMgr.Concatenate
                        (m_pidlPath,pidlDropDest);

    TCHAR szDropDest[MAX_PATH]=_T("");
    DWORD dwSize=MAX_PATH;
    if(pidlComplexDest) //Dest is not the root folder
    {
        HR(m_PidlMgr.GetFullName(pidlComplexDest,szDropDest,&dwSize));
        if(dwSize == 0)
        {
            m_PidlMgr.Delete(pidlComplexDest);
            return E_FAIL;
        }
    }

    DWORD dwFileOpFlags = 0;
    // Iterate over HDROP information
    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);

    //Check whether dragged object include folder?
    for( UINT i=0; i<nFiles; i++ )
    {
        // Get dragged filename
        if( ::DragQueryFile(hDrop, i, szFileName, MAX_PATH)==0 )
        {
            m_PidlMgr.Delete(pidlComplexDest);
            ::GlobalUnlock(hMem);
            return E_FAIL;
        }
        // dragged a folder
        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;
        }
    }

    // Handle the dragged files.
    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) //Dest is not the root folder
    {
        HR(m_PidlMgr.GetFullName(pidlComplexDest,szDropDest,&dwSize));
        if(dwSize == 0)
        {
            m_PidlMgr.Delete(pidlComplexDest);
            return E_FAIL;
        }
    }

    //Fetch Complex PIDL of dragged item from data object
    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 drag and drop in same path, do nothing
    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; //provide the drop target service
HWND m_hwndList;            //listview handler

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)
{
    ...... //create host window for Shell View, 
       //which  will send a WM_CREATE message
           //and in handling WM_CREATE message, listview will be created

    //to be the drop target
    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)
{
    //Unregister the drag drop
    ::RevokeDragDrop(m_hwndList);
    m_pDropTarget->Release();

    // Make absolutely sure all our UI is cleaned up.
    UIActivate(SVUIA_DEACTIVATE);

    ::DestroyWindow(m_hWnd);

    // Release the shell browser object
    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:

  1. Find the item to be dragged
  2. Prepare the source object and the data object according to the dragged item
  3. Call the DoDragDrop
  4. Delete the dragged file from current listview.

LRESULT CNSFShellView::OnBeginDrag(UINT ClitID, LPNMHDR lpnmh, BOOL& bHandled)
{
    // Find out the dragged item in listview
    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);

            //Drag a folder from right panel is not allowed
            if(NWS_FOLDER == m_PidlMgr.GetItemType(pidlSelected))
                return E_FAIL;
        }
    }

    // Create a IDataObject according to the selected item
    CComObject<CNSFDATAOBJECT>* pDataObject;
    HR( CComObject<CNSFDATAOBJECT>::CreateInstance(&pDataObject) );
    pDataObject->AddRef();
    //Called by our ShellView, pidl should be a complex pidl
    LPITEMIDLIST pidlComplex;
    pidlComplex = m_PidlMgr.Concatenate(m_pFolder->m_pidlPath,pidlSelected);
    HR( pDataObject->_Init(m_pFolder, pidlComplex) );

    if( SUCCEEDED(Hr) && (pDataObject!=NULL) )
    {
        // If we're dragging then call Window's DoDragDrop()
        // method with a freshly created IDropSource object
        CComObject<CNSFDROPSOURCE>* pDropSource;
        HR( CComObject<CNSFDROPSOURCE>::CreateInstance(&pDropSource) );
        pDropSource->AddRef();

        // DoDragDrop, Only allows DROPEFFECT_MOVE....
        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 Smile | :) . 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.

A list of licenses authors might use can be found here

Share

About the Author

zengxi

China China
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.

Comments and Discussions

 
QuestionLVN_BEGINDRAG notification PinmemberRSL-REK8-Sep-06 12:17 
AnswerRe: LVN_BEGINDRAG notification PinmemberAntti Keskinen8-Jan-07 0:07 
GeneralLVN_BEGINDRAG notification C# PinmemberRSL-REK8-Sep-06 6:23 
GeneralIn NSE, use IShellFolderViewCB to add custom toolbar buttons! Pinmemberliugd13-Jun-06 6:32 
GeneralI fail to drag drop my file into PPT! PinmemberFrank K Fang20-May-06 22:29 
GeneralRe: I fail to drag drop my file into PPT! Pinmemberzengxi21-May-06 3:24 
Generalusing DoModal PinmemberFarishtagammerpaajee7-May-06 14:22 
GeneralRe: using DoModal PinmemberFarishtagammerpaajee7-May-06 15:06 
thanx God, i m done away with displaying dialog
now the problem is that i want to take the user values ( in edit box) on dialog, but the wizard dont allow me to add member variables with the edit boxes. plss tell me how to do this ??
Question Blocking!!!! My IAsyncOperation interface error or IDataObject:SetData error? Pinmemberfree2000fly27-Apr-06 16:18 
Generalwhen I drag & drop a large file (larger than 100m) Pinmemberfree2000fly21-Mar-06 17:00 
GeneralRe: when I drag & drop a large file (larger than 100m) Pinmemberfree2000fly23-Mar-06 17:17 
GeneralPlease, how to add button to toolbar of explorer???Urgent!! Pinmemberliugd19-Mar-06 21:24 
GeneralRe: Please, how to add button to toolbar of explorer???Urgent!! Pinmemberzengxi19-Mar-06 22:54 
Generalhello,how to do with DROPEFFECT_LINK Pinmemberhuomzh9-Mar-06 19:39 
GeneralRe: hello,how to do with DROPEFFECT_LINK Pinmemberzengxi9-Mar-06 20:15 
GeneralRe: hello,how to do with DROPEFFECT_LINK Pinmemberhuomzh9-Mar-06 21:11 
GeneralRe: hello,how to do with DROPEFFECT_LINK Pinmemberhuom17-Mar-06 18:14 
GeneralRe: hello,how to do with DROPEFFECT_LINK Pinmemberzengxi19-Mar-06 14:05 
GeneralHelp Me! Urgent!!! Pinmemberliugd2-Mar-06 15:04 
GeneralRe: Help Me! Urgent!!! Pinmemberzengxi2-Mar-06 23:15 
GeneralRe: Help Me! Urgent!!! Pinmemberliugd3-Mar-06 0:39 
GeneralRe: Help Me! Urgent!!! Pinmemberzengxi5-Mar-06 14:10 
GeneralThanks for the series! PinsitebuilderMichael Dunn23-Feb-06 14:47 
GeneralRe: Thanks for the series! PinmemberKarstenK27-Feb-06 1:32 
GeneralRe: Thanks for the series! Pinmemberzengxi1-Mar-06 21:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140826.1 | Last Updated 23 Feb 2006
Article Copyright 2006 by zengxi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid