|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionIn Parts I and II of the Guide, I showed how to write context menu extensions that are invoked when the user right-clicks on certain types of files. In this part, I'll demonstrate a different type of context menu extension, the drag and drop handler, which adds items to the context menu displayed for a right-button drag and drop operation. I'll also give more examples of using MFC in an extension. Part IV assumes that you understand the basics of shell extensions, and are familiar with MFC. This particular extension is a real utility that creates hard links on Windows 2000 and later, but you can still follow along even if you are using an older version of Windows. As every power user knows (and few normal users know), you can drag and drop items in Explorer using the right mouse button. When you release the button, Explorer shows a context menu that lists all the available actions you can take. Normally, these are move, copy, and create shortcut:
Explorer lets us add items to this menu, by using a drag and drop handler. This type of extension is invoked when any right-drag and drop operation happens, and the extension can add menu items if it deems it should. An example of a drag and drop handler is in WinZip. Here's what WinZip adds to the context menu when you right-drag a compressed file:
WinZip's extension is invoked for any right-drag and drop operation, but it only adds it menu items if a compressed file is being dragged. This article's sample extension will use an API added in Windows 2000, 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. Using AppWizard to Get StartedRun the AppWizard and make a new ATL COM app. We'll call it HardLink. We are going to use MFC, so check
the Support MFC checkbox, and then click Finish. To add a COM object to the DLL, go to the ClassView
tree, right-click the HardLink classes item, and pick New ATL Object. (In VC 7, right-click the item
and pick Add|Add Class.) As before, choose Simple Object in the wizard, and use the name HardLinkShlExt
for the object. This will create a C++ class The Initialization InterfaceAs with our earlier context menu extensions, Explorer initializes us using the #include <comdef.h> #include <shlobj.h> class CHardLinkShlExt : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CHardLinkShlExt, &CLSID_HarkLinkShlExt>, public IShellExtInit { BEGIN_COM_MAP(CHardLinkShlExt) COM_INTERFACE_ENTRY(IShellExtInit) END_COM_MAP() public: // IShellExtInit STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY); We'll also need some variables to hold a bitmap and the names of the files being dragged: protected:
CBitmap m_bitmap;
TCHAR m_szFolderDroppedIn[MAX_PATH];
CStringList m_lsDroppedFiles;
Also, we'll need to add a some #define WINVER 0x0500 #define _WIN32_WINNT 0x0500 #define _WIN32_IE 0x0400 Defining Now, on to the HRESULT IShellExtInit::Initialize ( LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID ); For drag and drop extensions, Our first step is to load a bitmap for our menu item. Then, we attach a HRESULT CHardLinkShlExt::Initialize (
LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj,
HKEY hProgID )
{
AFX_MANAGE_STATE(AfxGetStaticModuleState()); // init MFC
COleDataObject dataobj;
HGLOBAL hglobal;
HDROP hdrop;
TCHAR szRoot[MAX_PATH];
TCHAR szFileSystemName[256];
TCHAR szFile[MAX_PATH];
UINT uFile, uNumFiles;
m_bitmap.LoadBitmap ( IDB_LINKBITMAP );
dataobj.Attach ( pDataObj, FALSE );
Passing "PIDL" is an acronym for pointer to an ID list. A PIDL is a way of uniquely identifying any object within the hierarchy presented by Explorer. Every object in the shell, whether it's part of the file system or not, has a PIDL. The exact structure of a PIDL depends on where the object is, but unless you are writing your own namespace extension, you don't (and shouldn't) have to worry about the internal structure of a PIDL. For our purposes, we can use the shell API if ( !SHGetPathFromIDList(pidlFolder, m_szFolderDroppedIn) ) return E_FAIL; Next, we have to check if the target directory is on an NTFS volume. We get the root component of the path (for
example, lstrcpy ( szRoot, m_szFolderDroppedIn ); PathStripToRoot ( szRoot ); if ( !GetVolumeInformation ( szRoot, NULL, 0, NULL, NULL, NULL, szFileSystemName, 256 )) { // Couldn't determine file system type. return E_FAIL; } if ( 0 != lstrcmpi ( szFileSystemName, _T("ntfs") )) { // The file system isn't NTFS, so it doesn't support hard links. return E_FAIL; } Next, we get an hglobal = dataobj.GetGlobalData ( CF_HDROP ); if ( NULL == hglobal ) return E_INVALIDARG; hdrop = (HDROP) GlobalLock ( hglobal ); if ( NULL == hdrop ) return E_INVALIDARG; We then use the uNumFiles = DragQueryFile ( hdrop, 0xFFFFFFFF, NULL, 0 ); for ( uFile = 0; uFile < uNumFiles; uFile++ ) { if ( DragQueryFile ( hdrop, uFile, szFile, MAX_PATH ) ) { if ( PathIsDirectory ( szFile ) ) { // We found a directory! Bail out. m_lsDroppedFiles.RemoveAll(); break; } We also have to check that the dropped files reside on the same volume as the target directory. What I did was compare the root components of each file and the target directory, and return if they are different. This is not a complete solution, though, since on NTFS volumes, you can mount a volume in the middle of another. For example, you could have a C: volume, and mount another volume as C:\dev. This code will not reject an attempt to make a link from C:\dev to somewhere else on C:. Here's the check that compares the root components: if ( !PathIsSameRoot(szFile, m_szFolderDroppedIn) ) { // Dropped files came from a different volume - bail out. m_lsDroppedFiles.RemoveAll(); break; } If the file passes both checks, we add it to // Add the file to our list of dropped files. m_lsDroppedFiles.AddTail ( szFile ); } } // end for After the for loop, we release resources and return back to Explorer. If the string list contains any filenames,
we return GlobalUnlock ( hglobal ); return (m_lsDroppedFiles.GetCount() > 0) ? S_OK : E_FAIL; } Modifying the Context MenuLike other context menu extensions, a drag and drop handler implements the class CHardLinkShlExt : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CHardLinkShlExt, &CLSID_HardLinkShlExt>, public IShellExtInit, public IContextMenu { BEGIN_COM_MAP(CHardLinkShlExt) COM_INTERFACE_ENTRY(IShellExtInit) COM_INTERFACE_ENTRY(IContextMenu) END_COM_MAP() public: // IContextMenu STDMETHODIMP GetCommandString(UINT, UINT, UINT*, LPSTR, UINT) { return E_NOTIMPL; } STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO); STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT); Note that we don't need any code in Explorer calls our HRESULT CHardLinkShlExt::QueryContextMenu (
HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd,
UINT uidLastCmd, UINT uFlags )
{
AFX_MANAGE_STATE(AfxGetStaticModuleState()); // init MFC
// If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
if ( uFlags & CMF_DEFAULTONLY )
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );
// Add the hard link menu item.
InsertMenu ( hmenu, uMenuIndex, MF_STRING | MF_BYPOSITION,
uidFirstCmd, _T("Create hard link(s) here") );
if ( NULL != m_bitmap.GetSafeHandle() )
{
SetMenuItemBitmaps ( hmenu, uMenuIndex, MF_BYPOSITION,
(HBITMAP) m_bitmap.GetSafeHandle(), NULL );
}
// Return 1 to tell the shell that we added 1 top-level menu item.
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}
Here's what the new menu item looks like:
Making the LinkExplorer calls First, the locals and a check of the HRESULT CHardLinkShlExt::InvokeCommand (
LPCMINVOKECOMMANDINFO pInfo )
{
AFX_MANAGE_STATE(AfxGetStaticModuleState()); // init MFC
TCHAR szNewFilename[MAX_PATH+32];
CString sSrcFile;
TCHAR szSrcFileTitle[MAX_PATH];
CString sMessage;
UINT uLinkNum;
POSITION pos;
// Double-check that we're getting called for our own menu item - lpVerb
// must be 0.
if ( 0 != pInfo->lpVerb )
return E_INVALIDARG;
Next, we get a pos = m_lsDroppedFiles.GetHeadPosition(); ASSERT ( NULL != pos );
while ( NULL != pos ) { // Get the next source filename. sSrcFile = m_lsDroppedFiles.GetNext ( pos ); // Remove the path - this reduces "C:\xyz\foo\stuff.exe" to "stuff.exe" lstrcpy ( szSrcFileTitle, sSrcFile ); PathStripPath ( szSrcFileTitle ); // Make the filename for the hard link - we'll first try // "Hard link to stuff.exe" wsprintf ( szNewFilename, _T("%sHard link to %s"), m_szFolderDroppedIn, szSrcFileTitle );
At this point, for ( uLinkNum = 2; PathFileExists(szNewFilename) && uLinkNum < 100; uLinkNum++ ) { // Try another filename for the link. wsprintf ( szNewFilename, _T("%sHard link (%u) to %s"), m_szFolderDroppedIn, uLinkNum, szSrcFileTitle ); // If the resulting filename is longer than MAX_PATH, show an // error message. if ( lstrlen(szNewFilename) >= MAX_PATH ) { sMessage.Format ( _T("Failed to make a link to %s.\nDo you want to continue making links?"), (LPCTSTR) sSrcFile ); if (IDNO == MessageBox ( pInfo->hwnd, sMessage, _T("Create Hard Links"), MB_ICONQUESTION | MB_YESNO) ) break; else continue; } } // end for The message box lets you abort the entire operation if you want. Next, we check to see if we hit the limit of 99 links. Again, we let the user abort the whole operation. if ( 100 == uLinkNum ) { sMessage.Format ( _T("Failed to make a link to %s.\nDo you want to continue making links?"), (LPCTSTR) sSrcFile ); if (IDNO == MessageBox ( pInfo->hwnd, sMessage, _T("Create Hard Links"), MB_ICONQUESTION | MB_YESNO) ) break; else continue; } All that's left is to make the hard link. I've omitted the error handling code for clarity. CreateHardLink ( szNewFilename, sSrcFile, NULL );
} // end while
return S_OK;
}
The hard link doesn't look any different in Explorer, it just looks like any other ordinary file. But if you modify one copy, the changes will be reflected in the other copy.
Registering the Shell ExtensionRegistering a drag and drop handler is simpler than other context menu extensions. All handlers are registered
under the Here is the RGS script to handle all three of the above situations: HKCR
{
NoRemove Directory
{
NoRemove shellex
{
NoRemove DragDropHandlers
{
ForceRemove HardLinkShlExt = s '{3C06DFAE-062F-11D4-8D3B-919D46C1CE19}'
}
}
}
NoRemove Folder
{
NoRemove shellex
{
NoRemove DragDropHandlers
{
ForceRemove HardLinkShlExt = s '{3C06DFAE-062F-11D4-8D3B-919D46C1CE19}'
}
}
}
NoRemove Drive
{
NoRemove shellex
{
NoRemove DragDropHandlers
{
ForceRemove HardLinkShlExt = s '{3C06DFAE-062F-11D4-8D3B-919D46C1CE19}'
}
}
}
}
As with our previous extensions, on NT-based OSes, we need to add our extension to the list of "approved"
extensions. The code to do this is in the If You Don't Have Windows 2000/NTFSYou can still build and run the sample project on earlier versions of Windows, or if you don't have an NTFS volume available. Just open the stdafx.h file, and uncomment the line that reads: // #define NOT_ON_WIN2K That will make the extension skip the file system check (so it will run on anything, not just NTFS), and display message boxes instead of actually making links. To Be ContinuedComing up in Part V, we'll see a new type of extension, the property sheet handler, which adds pages to the properties dialog for files. Copyright and LicenseThis 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 HistoryApril 3, 2000: Article first published. | ||||||||||||||||||||