Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Complete Idiot's Guide to Writing Shell Extensions - Part VI

0.00/5 (No votes)
26 May 2006 2  
A tutorial on writing a shell extension that can be used on the Send To menu.

Contents

Introduction

Here in Part VI of the Guide, I'll introduce a lesser-used type of shell extension, the drop handler. This type can be used to add drag-and-drop functionality to Explorer, where the file being dropped on (the drop target) determines the extension that gets invoked.

This article assumes that you understand the basics of shell extensions, and are familiar with the MFC classes used to interact with the shell. If you need a refresher on the MFC classes, you should read Part IV, since the same techniques will be used in this article.

Remember that VC 7 (and probably VC 8) users will need to change some settings before compiling. See the README section in Part I for the details.

Back in Part IV, I talked about the drag and drop handler, which is invoked during a right-button drag-and-drop operation. Explorer also lets us write an extension that is invoked during a left-button drag-and-drop operation, where the extension operates on the file that is being dropped on. For example, WinZip contains a drop handler that lets you add files to a zip by dropping them on the zip. When you drag files over a zip, Explorer indicates that the zip is a drop target by highlighting the zip file and changing the cursor so it has a + icon:

 [Dropping on a zip - 3K]

If there were no drop handler, then nothing would happen when you tried to drag files over a zip:

 [Dropping on a zip w/o a drop handler - 3K]

Drop handlers are really only useful in this way if you have your own file type, like WinZip does. A much more interesting thing to do with drop handlers is add items to the Send To menu. The Send To menu shows the contents of the SendTo menu (on Windows 9x, this directory is under the Windows directory; on NT-based OSes, this directory is in the user's profile directory). In older versions of Windows, the SendTo directory contains just shortcuts, but newer versions (and other apps like the Shell Power Toys) add several other items, as shown here:

 [Sample Send To menu - 3K]

If you don't seen how drop handlers figure into this, take a look at a directory listing of the SendTo directory:

12/23/05   3:39a    129  3½ Floppy (A).lnk
12/28/05   1:42p      0  Desktop (create shortcut).DeskLink
12/28/05   1:42p      0  Mail Recipient.MAPIMail
12/23/05  12:31p      0  My Documents.mydocs
 5/25/06  11:05a  1,267  Notepad.lnk

Notice those weird extensions like ".DeskLink". Those zero-byte files are there to cause the items to appear in the Send To menu, and the extensions are listed in the registry. They are not normal associations, though, since the files have no verbs like open or print. What they do have are drop handlers. When you select one of those items on the Send To menu, Explorer invokes the associated drop handler. Here's another look at the menu, with the drop targets indicated:

 [Send To menu again - 5K]

This article's sample project will be a clone of the old "Send To Any Folder" Powertoy - it will copy or move files to any directory on your computer.

The Initialization Interface

You should be familiar with the set-up steps now, so I'll skip the instructions for going through the VC wizards. If you're following along in the wizards, make a new ATL COM app called SendToClone, with a C++ implementation class CSendToShlExt.

Since a drop handler is invoked on just one file (the file being dropped on), initialization is done through the IPersistFile interface. (Recall that IPersistFile is used for extensions that can only ever operate on a single file.) IPersistFile has a lot of methods, but in shell extensions, only the Load() method needs an implementation.

We'll need to add IPersistFile to the list of interfaces that CSendToShlExt implements. Open SendToShlExt.h and add the lines listed here in bold:

#include <comdef.h>

#include <shlobj.h>

 
class CSendToShlExt : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CSendToShlExt, &CLSID_SendToShlExt>,
  public IPersistFile
{
  BEGIN_COM_MAP(CSendToShlExt)
    COM_INTERFACE_ENTRY(IPersistFile)
  END_COM_MAP()
 
public:
  // IPersistFile

  STDMETHOD(GetClassID)(LPCLSID)      { return E_NOTIMPL; }
  STDMETHOD(IsDirty)()                { return E_NOTIMPL; }
  STDMETHOD(Load)(LPCOLESTR, DWORD)   { return S_OK;      }
  STDMETHOD(Save)(LPCOLESTR, BOOL)    { return E_NOTIMPL; }
  STDMETHOD(SaveCompleted)(LPCOLESTR) { return E_NOTIMPL; }
  STDMETHOD(GetCurFile)(LPOLESTR*)    { return E_NOTIMPL; }

Notice that Load() doesn't do anything besides returning S_OK. The Load() method receives the full path of the file that is the drop target, but for this extension we don't care about that filename, so we can ignore it.

Taking Part in the Drag and Drop Operation

In order to do its job, our extension has to communicate with the drop source, which is Explorer itself. Our extension gets a list of files being dragged, and it tells Explorer whether it will accept the files if the user drops them. This communication happens through an interface, naturally: IDropTarget. The IDropTarget methods are:

  • DragEnter(): Called when the user first drags over a file. The return value tells Explorer whether the extension will accept the files if they are dropped.
  • DragOver(): Not called in shell extensions.
  • DragLeave(): Called when the user drags off of the target file without dropping.
  • Drop(): Called when the user drops onto the target file. This is where the shell extension does its work.

To add IDropTarget to CSendToShlExt, open SendToShlExt.h and add the lines listed here in bold:

class CSendToShlExt : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CSendToShlExt, &CLSID_SendToShlExt>,
  public IPersistFile,
  public IDropTarget
{
  BEGIN_COM_MAP(CSendToShlExt)
    COM_INTERFACE_ENTRY(IPersistFile)
    COM_INTERFACE_ENTRY(IDropTarget)
  END_COM_MAP()
 
public:
  // IDropTarget

  STDMETHODIMP DragEnter(IDataObject* pDataObj, DWORD grfKeyState,
                         POINTL pt, DWORD* pdwEffect);
 
  STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    { return E_NOTIMPL; }
 
  STDMETHODIMP DragLeave();
 
  STDMETHODIMP Drop(IDataObject* pDataObj, DWORD grfKeyState,
                    POINTL pt, DWORD* pdwEffect);
 
protected:
  CStringList m_lsDroppedFiles;
}

As in the earlier extensions, we'll use a list of strings to hold the names of the files being dragged. The DragOver() method needs no implementation, since it is not called. I'll cover the other three methods next.

DragEnter()

The prototype for DragEnter() is:

HRESULT IDropTarget::DragEnter (
  IDataObject* pDataObj,
  DWORD        grfKeyState,
  POINTL       pt,
  DWORD*       pdwEffect );

pDataObj is an IDataObject interface with which we can enumerate the files being dragged. grfKeyState is a set of flags indicating which shift keys and mouse buttons are pressed. pt is a POINTL struct (which happens to be the same as a POINT) that holds the mouse coordinates. pdwEffect is a pointer to a DWORD in which we will return a value that tells Explorer if we will accept the drop, and if so, what kind of icon to overlay on the mouse cursor.

As I mentioned earlier, DragEnter() is normally called when the user first drags over a drop target. However, it is also called when the user clicks a Send To menu item, so we can still do all our work in DragEnter() even though there is technically no drag and drop happening.

Our DragEnter() implementation will fill a string list with the names of the files being dragged. This extension will accept all files or directories, since any file system object can be copied or moved.

DragEnter() starts off with stuff that should be familiar to you now - we attach a COleDataObject to the IDataObject interface, and enumerate the files being dragged.

STDMETHODIMP CSendToShlExt::DragEnter (
    IDataObject* pDataObj,
    DWORD        grfKeyState,
    POINTL       pt,
    DWORD*       pdwEffect )
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());  // init MFC

 
COleDataObject dataobj;
TCHAR          szItem[MAX_PATH];
UINT           uNumFiles;
HGLOBAL        hglobal;
HDROP          hdrop;
 
  dataobj.Attach ( pDataObj, FALSE );
 
  // Read the list of items from the data object.  They're stored in HDROP

  // form, so just get the HDROP handle and then use the drag 'n' drop APIs

  // on it.

  hglobal = dataobj.GetGlobalData ( CF_HDROP );
 
  if ( NULL != hglobal )
    {
    hdrop = (HDROP) GlobalLock ( hglobal );
 
    uNumFiles = DragQueryFile ( hdrop, 0xFFFFFFFF, NULL, 0 );
 
    for ( UINT uFile = 0; uFile < uNumFiles; uFile++ )
      {
      if ( 0 != DragQueryFile ( hdrop, uFile, szItem, MAX_PATH ))
        m_lsDroppedFiles.AddTail ( szItem );
      }
 
    GlobalUnlock ( hglobal );
    }

Now it's time to return a value in pdwEffect. The effects we can return are:

  • DROPEFFECT_COPY: Tell Explorer that the dragged files will be copied by our extension.
  • DROPEFFECT_MOVE: Tell Explorer that the dragged files will be moved by our extension.
  • DROPEFFECT_LINK: Tell Explorer that the dragged files will be linked to by our extension.
  • DROPEFFECT_NONE: Tell Explorer that we won't accept the files being dragged.

The only effect we'll return is DROPEFFECT_COPY. We can't return DROPEFFECT_MOVE, because that would make Explorer delete the files that were dropped. We could return DROPEFFECT_LINK, but the cursor would have the little arrow that normally indicates shortcuts, and that would be misleading to the user. If the file list is empty, which happens if we can't read the clipboard, we return DROPEFFECT_NONE to tell Explorer we won't accept the drop.

  if ( m_lsDroppedFiles.GetCount() > 0 ) 
    {
    *pdwEffect = DROPEFFECT_COPY;
    return S_OK;
    }
  else
    {
    *pdwEffect = DROPEFFECT_NONE;
    return E_INVALIDARG;
    }
}

DragLeave()

DragLeave() is called if the user drags away from our target file without dropping. This method isn't used by the Send To menu, but it is called if you have an Explorer window open on the Send To directory and drag onto the files in that directory. We don't have any cleanup tasks, so all we have to do is return S_OK:

STDMETHODIMP CSendToShlExt::DragLeave()
{
  return S_OK;
}

Drop()

If the user selects our Send To menu item, Explorer calls Drop(), which has this prototype:

HRESULT IDropTarget::Drop (
    IDataObject* pDataObj,
    DWORD        grfKeyState,
    POINTL       pt,
    DWORD*       pdwEffect );

The first three parameters are the same as DragEnter(). Drop() should return the final effect of the operation through the pdwEffect parameter. Our Drop() function creates the main dialog and passes it the list of filenames. The dialog does all the work, and when DoModal() returns, we set the final drop effect.

STDMETHODIMP CSendToShlExt::Drop (
    IDataObject* pDataObj,
    DWORD        grfKeyState,
    POINTL       pt,
    DWORD*       pdwEffect )
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());  // init MFC

 
CSendToCloneDlg dlg ( &m_lsDroppedFiles );
 
    dlg.DoModal();
 
    *pdwEffect = DROPEFFECT_COPY;
    return S_OK;
}

The dialog looks like this:

 [Send to dialog - 14K]

It's a pretty straightforward MFC dialog, and you can find the source in the SendToCloneDlg.cpp file. I did the actual moving and copying using the CShellFileOp class from my article "CShellFileOp - Wrapper for SHFileOperation."

As with the extension in Part II, we need to add a manifest to the DLL's resources so our dialog gets themed on XP. However, just adding the manifest isn't enough in this case, because the code that creates and manages the dialog is in MFC. MFC isn't compiled with the ISOLATION_AWARE_ENABLED symbol defined, so the code doesn't use the IsolationAwareXxx wrappers that are necessary to hook up themes.

There is a good explanation of the situation in a post from Dave Anderson in this newsgroup thread. To summarize, before we show the dialog, we need to use the activation context APIs so Windows uses our manifest and uses version 6 of the common controls for our dialog. This code is encapsulated in the CActCtx class, and used in our Drop() method:

STDMETHODIMP CSendToShlExt::Drop(...)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());  // init MFC

 
CSendToCloneDlg dlg ( &m_lsDroppedFiles );
CAxtCtx ctx;
 
    dlg.DoModal();
 
    *pdwEffect = DROPEFFECT_COPY;
    return S_OK;
}"/shell/cshellfileop.asp">

After adding this code, the dialog becomes properly themed:

 [Send to dialog (themed) - 16K]

But wait -- how do we tell Explorer about our drop handler? And how do we get an item in the Send To menu? I'll explain how to do this in the next section.

Registering the Extension

Registering a drop handler is a bit different than other types of extensions, since it requires creating a new association under HKEY_CLASSES_ROOT. The AppWizard-generated RGS script is listed below. Parts which you should add or change are listed in bold.

HKCR
{
  .SendToClone = s 'CLSID\{B7F3240E-0E29-11D4-8D3B-80CD3621FB09}'
  NoRemove CLSID
  {
    ForceRemove {B7F3240E-0E29-11D4-8D3B-80CD3621FB09} = s 'Send To Any Folder Clone'
    {
      InprocServer32 = s '%MODULE%'
      {
        val ThreadingModel = s 'Apartment'
      }
      val NeverShowExt = s ''
      DefaultIcon = s '%MODULE%,0'
      shellex
      {
        DropHandler = s '{B7F3240E-0E29-11D4-8D3B-80CD3621FB09}'
      }
    }
  }
}

The first line is the one that makes the association. It creates a new extension, .SendToClone, that will be used by our drop target. Note that the default value of the .SendToClone key is prefixed with "CLSID\". This tells Explorer that the data that describes the association is in a key under HKCR\CLSID. With more conventional associations, that stuff is stored in a key right under HKEY_CLASSES_ROOT (for example, the .txt key points at the HKCR\txtfile key), but it seems to be a convention to store a drop handler's association data under its CLSID key, to keep all the data in one place.

The "Send To Any Folder Clone" string is the file type description that appears in Explorer if you browse to the SendTo directory. The NeverShowExt value is created to tell Explorer that it should not show the ".SendToClone" extension. The DefaultIcon key lists the location of the icon to use for .SendToClone files. Finally we have the familiar shellex key with the DropHandler subkey. Since there can be only one drop handler for a file type, the handler's GUID is stored right in the DropHandler key, instead of being listed in a subkey under DropHandler.

The only remaining detail is to create a file in the SendTo directory so our menu item appears. We can do this in DllRegisterServer() and delete the file in DllUnregisterServer(). Here's the code to create the file:

LPITEMIDLIST pidl;
TCHAR        szSendtoPath[MAX_PATH];
HANDLE       hFile;
LPMALLOC     pMalloc;
 
  if ( SUCCEEDED( SHGetSpecialFolderLocation ( NULL, CSIDL_SENDTO, &pidl ) ))
    {
    if ( SHGetPathFromIDList ( pidl, szSendtoPath ) )
      {
      PathAppend ( szSendtoPath, _T("Some other folder.SendToClone") );
 
      hFile = CreateFile ( szSendtoPath, GENERIC_WRITE, FILE_SHARE_READ,
                           NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
 
      CloseHandle ( hFile );
      }
 
    if ( SUCCEEDED( SHGetMalloc ( &pMalloc ) ))
      {
      pMalloc->Free ( pidl );
      pMalloc->Release();
      }
    }

Here's what the new Send To menu item looks like, with our own item:

 [New Send To item - 3K]

DllUnregisterServer() has similar code that deletes the file. The code above will work on all versions of Windows (well, all versions 4 and above). If you know your code will be run on only shell version 4.71 and higher, you can use the SHGetSpecialFolderPath() function instead of SHGetSpecialFolderLocation().

As always, on NT-based OSes, we need to add our extension to the list of "approved" extensions. The code to do this is in the DllRegisterServer() and DllUnregisterServer() functions in the sample project.

To Be Continued...

Coming up in Part VII, I'll answer some reader requests and demonstrate two new variations of context menu extensions.

Copyright and License

This article is copyrighted material, ©2000-2006 by Michael Dunn. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here.

The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefitting from my code) but is not required. Attribution in your own source code is also appreciated but not required.

Revision History

April 20, 2000: Article first published.
June 6, 2000: Something updated. ;)
May 26, 2006: Updated to cover changes in VC 7.1, cleaned up code snippets, sample project gets themed on XP.

Series Navigation: « Part V | Part VII »

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