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





5.00/5 (30 votes)
Sep 1, 2000

536649

5136
A tutorial on using owner-drawn menus in a context menu shell extensions, and on making a context menu extension that responds to a right-click in a directory background.
Contents
- Introduction
- Extension 1 - An Owner-Drawn Menu Item
- Extension 2 - Handling a right-click in a directory window
- To Be Continued
- Copyright and License
- Revision History
Introduction
In this part of the Idiot's Guide, I'll answer some reader requests and write about two topics: Using owner-drawn menus in a context menu extension, and making a context menu extension that's invoked when the user right-clicks the background of a directory window. You should read and understand Part I and Part II, so you understand the fundamentals of context menu extensions.
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.
Extension 1 - An Owner-Drawn Menu Item
In this section, I'll just cover the additional work required to implement owner-drawn menus.
Since this extension will have an owner-drawn menu, it's got to be something graphical. I decided to copy something done by the program PicaView[1]: Showing a thumbnail of a BMP file in its context menu. Here's what PicaView's menu items look like:
This extension will create a thumbnail for BMP files, although to keep the code simple, I won't worry about getting the proportions or the colors exactly right. Fixing that up is left as an exercise for the reader. ;)
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 (with MFC support enabled) called BmpViewerExt,
with a C++ implementation class CBmpCtxMenuExt
.
As in the previous context menu extensions, this extension will implement the IShellExtInit
interface.
To add IShellExtInit
to our COM object, open BmpCtxMenuExt.h and add the lines listed here
in bold. There are also several member variables listed, which we'll use later while drawing our menu item.
#include <comdef.h> ///////////////////////////////////////////////////////////////////////////// // CBmpCtxMenuExt class CBmpCtxMenuExt : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CBmpCtxMenuExt, &CLSID_BmpCtxMenuExt>, public IShellExtInit { BEGIN_COM_MAP(CBmpCtxMenuExt) COM_INTERFACE_ENTRY(IShellExtInit) END_COM_MAP() public: // IShellExtInit STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY); protected: TCHAR m_szFile[MAX_PATH]; CBitmap m_bmp; UINT m_uOurItemID; LONG m_lItemWidth, m_lItemHeight; LONG m_lBmpWidth, m_lBmpHeight; static const LONG m_lMaxThumbnailSize; static const LONG m_l3DBorderWidth; static const LONG m_lMenuItemSpacing; static const LONG m_lTotalBorderSpace; // Helper functions for handling the menu-related messages. STDMETHODIMP MenuMessageHandler(UINT, WPARAM, LPARAM, LRESULT*); STDMETHODIMP OnMeasureItem(MEASUREITEMSTRUCT*, LRESULT*); STDMETHODIMP OnDrawItem(DRAWITEMSTRUCT*, LRESULT*); };
What we'll do in IShellExtInit::Initialize()
is get the name of the file that was right-clicked,
and if its extension is .BMP, create a thumbnail for it.
In the BmpCtxMenuExt.cpp file, add declarations for those static variables. These control visual aspects of the thumbnail and its borders. Feel free to change these around, and watch how they affect the final result in the menu.
const LONG CBmpCtxMenuExt::m_lMaxThumbnailSize = 64; const LONG CBmpCtxMenuExt::m_l3DBorderWidth = 2; const LONG CBmpCtxMenuExt::m_lMenuItemSpacing = 4; const LONG CBmpCtxMenuExt::m_lTotalBorderSpace = 2*(m_lMenuItemSpacing+m_l3DBorderWidth);
These constants have the following meanings:
m_lMaxThumbnailSize
: If the bitmap is larger (in either dimension) than this number, it will be compressed down to fit in a square, where each edge of the square ism_lMaxThumbnailSize
pixels long. If the bitmap is smaller (in both dimensions), it will be drawn unchanged.m_l3DBorderWidth
: The thickness of the 3D border to be drawn around the thumbnail, in pixels.m_lMenuItemSpacing
: The number of pixels to leave blank around the 3D border. This provides a bit of space between the thumbnail and the surrounding menu items.
Also, add the definition of the IShellExtInit::Initialize()
function:
STDMETHODIMP CBmpCtxMenuExt::Initialize ( LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDO, HKEY hkeyProgID ) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); COleDataObject dataobj; HGLOBAL hglobal; HDROP hdrop; bool bOK = false; dataobj.Attach ( pDO, FALSE ); // Get the first selected file name. I'll keep this simple and just check // the first name to see if it's a .BMP. hglobal = dataobj.GetGlobalData ( CF_HDROP ); if ( NULL == hglobal ) return E_INVALIDARG; hdrop = (HDROP) GlobalLock ( hglobal ); if ( NULL == hdrop ) return E_INVALIDARG; // Get the name of the first selected file. if ( DragQueryFile ( hdrop, 0, m_szFile, MAX_PATH )) { // Is its extension .BMP? if ( PathMatchSpec ( m_szFile, _T("*.bmp") )) { // Load the bitmap and attach our CBitmap object to it. HBITMAP hbm = (HBITMAP) LoadImage ( NULL, m_szFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE ); if ( NULL != hbm ) { // We loaded the bitmap, so attach the CBitmap to it. m_bmp.Attach ( hbm ); bOK = true; } } } GlobalUnlock ( hglobal ); return bOK ? S_OK : E_FAIL; }
Pretty straightforward stuff here. We load the bitmap from the file and attach a CBitmap
object
to it, for later use.
Interacting With the Context Menu
As before, if IShellExtInit::Initialize()
returns S_OK
, Explorer queries our extension
for the IContextMenu
interface. To give our extension the ability to do its owner-draw menu items,
it also queries for IContextMenu3
. IContextMenu3
adds a method to IContextMenu
that we'll use to do our owner-drawing.
There is an IContextMenu2
interface, which the documentation claims is supported by shell
version 4.00. However, when I tested on Windows 95, the shell never queried for IContextMenu2
, so
the end result is you just can't do an owner-drawn menu item on shell version 4.00. (NT 4 might be different; I
don't have access to an NT 4 system to check.) Instead, you can make the menu item display a bitmap, but that results
in a less-than-optimal appearance when the item is selected. (This is what PicaView does on version 4.00.)
IContextMenu3
derives from IContextMenu2
, and adds the HandleMenuMsg2()
method to IContextMenu
. This method gives us a chance to respond to two menu messages: WM_MEASUREITEM
and WM_DRAWITEM
. The docs say that HandleMenuMsg2()
is called so it can handle WM_INITMENUPOPUP
and WM_MENUCHAR
as well, however during my testing, HandleMenuMsg2()
never received either
of those messages.
Since IContextMenu3
inherits from IContextMenu2
(which itself inherits from IContextMenu
),
we just need to have our C++ class derive from IContextMenu3
. Open up BmpCtxMenuExt.h and add
the lines listed here in bold:
class CBmpCtxMenuExt : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CBmpCtxMenuExt, &CLSID_BmpCtxMenuExt>, public IShellExtInit, public IContextMenu3 { BEGIN_COM_MAP(CSimpleShlExt) COM_INTERFACE_ENTRY(IShellExtInit) COM_INTERFACE_ENTRY(IContextMenu) COM_INTERFACE_ENTRY(IContextMenu2) COM_INTERFACE_ENTRY(IContextMenu3) END_COM_MAP() public: // IContextMenu STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT); STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO); STDMETHODIMP GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT); // IContextMenu2 STDMETHODIMP HandleMenuMsg(UINT, WPARAM, LPARAM); // IContextMenu3 STDMETHODIMP HandleMenuMsg2(UINT, WPARAM, LPARAM, LRESULT*);
Modifying the Context Menu
As before, we do our work in the three IContextMenu
methods. We add our menu item in QueryContextMenu()
.
We first check the shell version. If it's 4.71 or greater, then we can add an owner-drawn menu item. Otherwise,
we add an item that shows a bitmap. In the latter case, adding the item is all we have to do; the menu takes care
of showing the bitmap.
First, the code to check the shell version. It calls the DllGetVersion()
exported function to retrieve
the version. If that export is not present, then the DLL is version 4.00, since DllGetVersion()
wasn't
present in version 4.00.
STDMETHODIMP CBmpCtxMenuExt::QueryContextMenu ( HMENU hmenu, UINT uIndex, UINT uidCmdFirst, UINT uidCmdLast, UINT uFlags ) { // If the flags include CMF_DEFAULTONLY then we shouldn't do anything. if ( uFlags & CMF_DEFAULTONLY ) return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); bool bUseOwnerDraw = false; HINSTANCE hinstShell; hinstShell = GetModuleHandle ( _T("shell32") ); if ( NULL != hinstShell ) { DLLGETVERSIONPROC pProc; pProc = (DLLGETVERSIONPROC) GetProcAddress(hinstShell, "DllGetVersion"); if ( NULL != pProc ) { DLLVERSIONINFO rInfo = { sizeof(DLLVERSIONINFO) }; if ( SUCCEEDED( pProc ( &rInfo ) )) { if ( rInfo.dwMajorVersion > 4 || (rInfo.dwMajorVersion == 4 && rInfo.dwMinorVersion >= 71) ) bUseOwnerDraw = true; } } }
At this point, bUseOwnerDraw
indicates whether it's OK to use an owner-drawn item. If it is true,
we insert an owner-drawn item (see the line that sets mii.fType
). If it's false, we add a bitmap item,
and then tell the menu the handle of the bitmap to show.
MENUITEMINFO mii = {0}; mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_ID | MIIM_TYPE; mii.fType = bUseOwnerDraw ? MFT_OWNERDRAW : MFT_BITMAP; mii.wID = uidCmdFirst; if ( !bUseOwnerDraw ) { // NOTE: This will put the full-sized bitmap in the menu, which is // obviously a bit less than optimal. Scaling the bitmap down // to a thumbnail is left as an exercise. mii.dwTypeData = (LPTSTR) m_bmp.GetSafeHandle(); } InsertMenuItem ( hmenu, uIndex, TRUE, &mii ); // Store the menu item's ID so we can check against it later when // WM_MEASUREITEM/WM_DRAWITEM are sent. m_uOurItemID = uidCmdFirst; // Tell the shell we added 1 top-level menu item. return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1); }
We store the menu item ID in m_uOurItemID
so we will know the ID later on when other messages arrive.
This isn't strictly necessary since we only have one menu item, but it's good practice nonetheless, and it's absolutely
required if you have multiple items.
Showing Fly-by Help in the Status Bar
Displaying fly-by help is no different than in the earlier extensions. Explorer calls GetCommandString()
to retrieve a string that it shows in the status bar.
#include <atlconv.h> // for ATL string conversion macros STDMETHODIMP CBmpCtxMenuExt::GetCommandString ( UINT uCmd, UINT uFlags, UINT* puReserved, LPSTR pszName, UINT cchMax ) { USES_CONVERSION; static LPCTSTR szHelpString = _T("Select this thumbnail to view the entire picture."); // Check idCmd, it must be 0 since we have only one menu item. if ( 0 != uCmd ) return E_INVALIDARG; // If Explorer is asking for a help string, copy our string into the // supplied buffer. if ( uFlags & GCS_HELPTEXT ) { if ( uFlags & GCS_UNICODE ) { // We need to cast pszName to a Unicode string, and then use the // Unicode string copy API. lstrcpynW ( (LPWSTR) pszName, T2CW(szHelpString), cchMax ); } else { // Use the ANSI string copy API to return the help string. lstrcpynA ( pszName, T2CA(szHelpString), cchMax ); } } return S_OK; }
Carrying Out The User's Selection
The last method in IContextMenu
is InvokeCommand()
. This method is called if the user
clicks on the menu item we added. The extension will call ShellExecute()
to bring up the bitmap in
the program that is associated with .BMP files.
STDMETHODIMP CBmpCtxMenuExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pInfo ) { // If lpVerb really points to a string, ignore this // call and bail out. if ( 0 != HIWORD(pInfo->lpVerb) ) return E_INVALIDARG; // The command ID must be 0 since we only have one menu item. if ( 0 != LOWORD(pInfo->lpVerb) ) return E_INVALIDARG; // Open the bitmap in the default paint program. int nRet; nRet = (int) ShellExecute ( pInfo->hwnd, _T("open"), m_szFile, NULL, NULL, SW_SHOWNORMAL ); return (nRet > 32) ? S_OK : E_FAIL; }
Drawing the Menu Item
OK, so I bet you're bored by now with all the code you've seen before. Time to get to the new and interesting
stuff! The two methods added by IContextMenu2
and IContextMenu3
are listed below. They
just call through to a single helper function, which in turn calls a message handler. This is the method I devised
to keep from having two different versions of the message handlers (one for IContextMenu2
and IContextMenu3
).
There's a bit of strangeness with HandleMenuMsg2()
regarding the LRESULT*
parameter,
explained below in the comments.
STDMETHODIMP CBmpCtxMenuExt::HandleMenuMsg ( UINT uMsg, WPARAM wParam, LPARAM lParam ) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // init MFC // res is a dummy LRESULT variable. It's not used (IContextMenu2::HandleMenuMsg() // doesn't provide a way to return values), but it's here so that the code // in MenuMessageHandler() can be the same regardless of which interface it // gets called through (IContextMenu2 or 3). LRESULT res; return MenuMessageHandler ( uMsg, wParam, lParam, &res ); } STDMETHODIMP CBmpCtxMenuExt::HandleMenuMsg2 ( UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult ) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // init MFC // For messages that have no return value, pResult is NULL. // If it is NULL, I create a dummy LRESULT variable, so the code in // MenuMessageHandler() will always have a valid pResult pointer. if ( NULL == pResult ) { LRESULT res; return MenuMessageHandler ( uMsg, wParam, lParam, &res ); } else return MenuMessageHandler ( uMsg, wParam, lParam, pResult ); }
MenuMessageHandler()
just dispatches WM_MEASUREITEM
and WM_DRAWITEM
to
separate message handler functions.
HRESULT CBmpCtxMenuExt::MenuMessageHandler ( UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult ) { switch ( uMsg ) { case WM_MEASUREITEM: return OnMeasureItem ( (MEASUREITEMSTRUCT*) lParam, pResult ); break; case WM_DRAWITEM: return OnDrawItem ( (DRAWITEMSTRUCT*) lParam, pResult ); break; } return S_OK; }
As I noted earlier, the docs say that the shell should let our extension handle WM_INITMENUPOPUP
and WM_MENUCHAR
, but I have never seen those messages arrive during my testing.
Handling WM_MEASUREITEM
The shell sends our extension a WM_MEASUREITEM
message to request the dimensions of our menu item.
We start by checking that we're being called for our own menu item. If that test passes, we get the dimensions
of the bitmap, then calculate the size of the entire menu item.
First the bitmap size:
HRESULT CBmpCtxMenuExt::OnMeasureItem (
MEASUREITEMSTRUCT* pmis, LRESULT* pResult )
{
BITMAP bm;
LONG lThumbWidth, lThumbHeight;
// Check that we're getting called for our own menu item.
if ( m_uOurItemID != pmis->itemID )
return S_OK;
m_bmp.GetBitmap ( &bm );
m_lBmpWidth = bm.bmWidth;
m_lBmpHeight = bm.bmHeight;
Next, we calculate the thumbnail size, and from that, the size of the entire menu item. If the bitmap is smaller than the maximum thumbnail size (which, in the sample project, is 64x64) then it will be drawn as-is. Otherwise, it will be drawn at 64x64 resolution. This may distort the bitmap's appearance a bit; making the thumbnail look nice is left as an exercise.
// Calculate the bitmap thumbnail size. lThumbWidth = (m_lBmpWidth <= m_lMaxThumbnailSize) ? m_lBmpWidth : m_lMaxThumbnailSize; lThumbHeight = (m_lBmpHeight <= m_lMaxThumbnailSize) ? m_lBmpHeight : m_lMaxThumbnailSize; // Calculate the size of the menu item, which is the size of the thumbnail + // the border and padding (which provides some space at the edges of the item). m_lItemWidth = lThumbWidth + m_lTotalBorderSpace; m_lItemHeight = lThumbHeight + m_lTotalBorderSpace;
Now that we have the size of the menu item, we store the dimensions in the MENUITEMSTRUCT
that
we received with the message. Explorer will reserve enough space in the menu for our item.
pmis->itemWidth = m_lItemWidth;
pmis->itemHeight = m_lItemHeight;
*pResult = TRUE; // we handled the message
return S_OK;
}
Handling WM_DRAWITEM
When we receive a WM_DRAWITEM
message, Explorer is requesting that we actually draw our menu item.
We start off by calculating the RECT in which we'll draw the 3D border around the thumbnail. This RECT is not necessarily
the same size as what we returned from the WM_MEASUREITEM
handler, because the menu might be wider
than that size if there are other, larger items in the menu.
HRESULT CBmpCtxMenuExt::OnDrawItem ( DRAWITEMSTRUCT* pdis, LRESULT* pResult ) { CDC dcBmpSrc; CDC* pdcMenu = CDC::FromHandle ( pdis->hDC ); CRect rcItem ( pdis->rcItem ); // RECT of our menu item CRect rcDraw; // RECT in which we'll be drawing // Check that we're getting called for our own menu item. if ( m_uOurItemID != pdis->itemID ) return S_OK; // rcDraw will first be set to the RECT that we set in WM_MEASUREITEM. // It will get deflated as we go along. rcDraw.left = rcItem.CenterPoint().x - m_lItemWidth/2; rcDraw.top = rcItem.CenterPoint().y - m_lItemHeight/2; rcDraw.right = rcDraw.left + m_lItemWidth; rcDraw.bottom = rcDraw.top + m_lItemHeight; // Shrink rcDraw to account for the padding space around // the thumbnail. rcDraw.DeflateRect ( m_lMenuItemSpacing, m_lMenuItemSpacing );
The first drawing step is to color the background of the menu item. The itemState
member of the
DRAWITEMSTRUCT
indicates whether our item is selected or not. This determines what color we use for
the background.
// Fill in the background of the menu item.
if ( pdis->itemState & ODS_SELECTED )
pdcMenu->FillSolidRect ( rcItem, GetSysColor(COLOR_HIGHLIGHT) );
else
pdcMenu->FillSolidRect ( rcItem, GetSysColor(COLOR_MENU) );
Next, we draw a sunken border to make the thumbnail appear recessed into the menu.
// Draw the sunken 3D border.
for ( int i = 1; i <= m_l3DBorderWidth; i++ )
{
pdcMenu->Draw3dRect ( rcDraw, GetSysColor ( COLOR_3DDKSHADOW ),
GetSysColor(COLOR_3DHILIGHT) );
rcDraw.DeflateRect ( 1, 1 );
}
The last step is to draw the thumbnail itself. I took the simple route and did a simple StretchBlt()
call. The result isn't always pretty, but my goal was to keep the code simple.
// Create a new DC and select the original bitmap into it. CBitmap* pOldBmp; dcBmpSrc.CreateCompatibleDC ( &dc ); pOldBmp = dcBmpSrc.SelectObject ( &m_bmp ); // Blit the bitmap to the menu DC. pdcMenu->StretchBlt ( rcDraw.left, rcDraw.top, rcDraw.Width(), rcDraw.Height(), &dcBmpSrc, 0, 0, m_lBmpWidth, m_lBmpHeight, SRCCOPY ); dcBmpSrc.SelectObject ( pOldBmp ); *pResult = TRUE; // we handled the message return S_OK; }
Note that in a real shell extension, it would be a good idea to use a flicker-free drawing class, so that the menu item won't flicker as you move the mouse over it.
Here are some screen shots of the menus! First, the owner-drawn menu, in the unselected and selected states.
And here's the menu on shell version 4.00. Notice how the selected state inverts all the colors, making it kinda ugly.
Registering the shell extension
Registering our bitmap viewer is done the same way as our other context menu extensions. Here's the RGS script to do the job:
HKCR { NoRemove Paint.Picture { NoRemove ShellEx { NoRemove ContextMenuHandlers { BitmapPreview = s '{D6F469CD-3DC7-408F-BB5F-74A1CA2647C9}' } } } }
Note that the "Paint.Picture" file type is hard-coded here. If you don't use Paint as your default
viewer for .BMP files, you'll need to change "Paint.Picture" to whatever is stored in the default value
in the key HKCR\.bmp
. Needless to say, in production code, you should do this registration in DllRegisterServer()
,
so you can check whether "Paint.Picture" is the right key to write to. I have more to say about this
subject in Part I.
Extension 2 - Handling a Right-Click in a Directory Window
In shell version 4.71 and later, you can modify the context menu displayed when you right-click the desktop or any Explorer window that is viewing a file system directory. Programming this kind of extension is similar to other context menu extensions. The two major differences are:
- The parameters to
IShellExtInit::Initialize()
are used differently. - The extension is registered under a different key.
I won't go through all the steps again required for the extension. Check out the sample project if you want to see the whole thing.
Differences in IShellExtInit::Initialize()
Initialize()
has a pidlFolder
parameter which, until now, we've ignored because it's
been NULL. Now, finally, that parameter has some use! It is the PIDL of the directory where the right-click happened.
The second parameter (the IDataObject*
) is NULL, because there are no selected files to operate on.
Here's the implementation of Initialize()
from the sample project:
STDMETHODIMP CBkgndCtxMenuExt::Initialize ( LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDO, HKEY hkeyProgID ) { // We get the conventional path of the directory from its PIDL with the // SHGetPathFromIDList() API. return SHGetPathFromIDList(pidlFolder, m_szDirClickedIn) ? S_OK : E_INVALIDARG; }
The SHGetPathFromIDList()
function returns the full path of the folder, which we then store for
later use. It returns a BOOL
indicating success or failure.
Differences in IShellExtInit::GetCommandString()
Starting with XP, Explorer checks the verb names returned from GetCommandString()
, and removes
any items that have identical verbs. Our GetCommandString()
has to check the flags for GCS_VERB
,
and return a different verb name for each of our menu items:
STDMETHODIMP CBkgndCtxMenuExt::GetCommandString ( UINT uCmd, UINT uFlags, UINT* puReserved, LPSTR pszName, UINT cchMax ) { USES_CONVERSION; static LPCTSTR szItem0Verb = _T("CPBkgndExt0"); static LPCTSTR szItem1Verb = _T("CPBkgndExt1"); // Check idCmd, it must be 0 or 1 since we have two menu items. if ( uCmd > 1 ) return E_INVALIDARG; // Omitted: code to return a fly-by help string // Return verb names for our two items. if ( GCS_VERBA == uFlags ) lstrcpynA ( pszName, T2CA((0 == uCmd) ? szItem0Verb : szItem1Verb), cchMax ); else if ( GCS_VERBW == uFlags ) lstrcpynW ( (LPWSTR) pszName, T2CW((0 == uCmd) ? szItem0Verb : szItem1Verb), cchMax ); return S_OK; }
Differences in Registration
This type of extension is registered under a different key: HKCR\Directory\Background\ShellEx\ContextMenuHandlers
.
Here's the RGS script to do it:
HKCR { NoRemove Directory { NoRemove Background { NoRemove ShellEx { NoRemove ContextMenuHandlers { ForceRemove SimpleBkgndExtension = s '{9E5E1445-6CEA-4761-8E45-AA19F654571E}' } } } } }
Aside from these two differences, the extension works just like other context menu extensions. There is one
catch in IContextMenu::QueryContextMenu()
, though. On Windows 98 and 2000, the uIndex
parameter seems to always be -1 (0xFFFFFFFF). Passing -1 as the index to InsertMenu()
means the new
item goes at the bottom of the menu. However, if you increment uIndex
, it will become to zero, meaning
that if you pass uIndex
again to InsertMenu()
, the second item will appear at the top
of the menu. Check out the sample project's code for QueryContextMenu()
to see how to properly add
items in the correct place.
Here's what the modified context menu looks like, with two items added at the end. Note that IMHO, adding items
to the end of the menu in this way has major usability problems. When a user wants to select the Properties
item, it is a habitual action to right-click and then pick the last menu item. When our extension comes along and
adds items after Properties
, we render the user's habit invalid, which can cause frustration and generate
some nasty emails. ;)
To Be Continued...
Coming up in Part VIII, I'll demonstrate a column handler extension, which can add columns to Explorer's details view.
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
September 1, 2000: Article first published.
September 14, 2000: Something updated. ;)
May 30, 2006: Updated to cover changes in VC 7.1, cleaned up code snippets, sample project gets themed on XP.
Series Navigation: « Part VI | Part VIII »
End Notes
[1] PicaView was made by ACD Systems, but is no longer available.