|
Submenus in a context menu extension
In this article, I'll cover a tricky aspect of context menu extensions - submenus. The approach most people take at first to creating submenus leads to odd behavior in Explorer, but once you know the trick to make Explorer manage the menu correctly, it's easy! This article assumes you have a good grasp of context menu extensions. If you need a refresher, see part 1 and part 2 of my shell extension series.
Adding a submenu
This article's extension is a simple Open With... submenu, which has two items, Notepad and Internet Explorer. It behaves like the enhanced Open With menu in XP, and opens the selected file in the program that you pick. This Open With menu will demonstrate how to properly create a submenu in an extension.
What you might try first
The first thing that comes to mind is to create a new menu with CreatePopupMenu() and insert it into the menu provided by Explorer. HRESULT COpenWithCtxMenuExt::QueryContextMenu (
HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags )
{
if ( uFlags & CMF_DEFAULTONLY )
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );
HMENU hSubmenu = CreatePopupMenu();
UINT uID = uidFirstCmd;
InsertMenu ( hSubmenu, 0, MF_BYPOSITION, uID++, _T("&Notepad") );
InsertMenu ( hSubmenu, 1, MF_BYPOSITION, uID++, _T("&Internet Explorer") );
InsertMenu ( hmenu, uMenuIndex, MF_BYPOSITION | MF_POPUP,
(UINT_PTR) hSubmenu, _T("C&P Open With") );
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, uID - uidFirstCmd );
}
This actually works fine for the context menu, however the context menu items you add are duplicated on Explorer's File menu. If you invoke the context menu repeatedly, you'll see leftover popups on the File menu:
![[Leftover submenus - 12K]](CtxExtSubmenu/leftovers.gif)
The cause is related to how Explorer cleans up its menus after extensions are invoked. The return value of QueryContextMenu() tells Explorer how many items we add, and Explorer cleans up by calculating the IDs of our items and deleting them. Explorer knows the ID of the first item (since it passes us the value as uidCmdFirst) and can calculate the IDs of the others since the IDs are assumed to be consecutive. However, the popup menu has no ID, so Explorer doesn't delete it.
The correct way
The secret is to insert the submenu using the InsertMenuItem() API. What's different about using InsertMenuItem() is that you can give the submenu an ID, which isn't possible when you use InsertMenu(). Here's the corrected QueryContextMenu() code. HRESULT COpenWithCtxMenuExt::QueryContextMenu (
HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags )
{
if ( uFlags & CMF_DEFAULTONLY )
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );
HMENU hSubmenu = CreatePopupMenu();
UINT uID = uidFirstCmd;
InsertMenu ( hSubmenu, 0, MF_BYPOSITION, uID++, _T("&Notepad") );
InsertMenu ( hSubmenu, 1, MF_BYPOSITION, uID++, _T("&Internet Explorer") );
MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
mii.fMask = MIIM_SUBMENU | MIIM_STRING | MIIM_ID;
mii.wID = uID++;
mii.hSubMenu = hSubmenu;
mii.dwTypeData = _T("C&P Open With");
InsertMenuItem ( hmenu, uMenuIndex, TRUE, &mii );
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, uID - uidFirstCmd );
}
The return value this time is 3, which tells Explorer that we inserted 3 items (the two Open With items, and the submenu itself). Since all 3 items have IDs, Explorer can delete them all and completely remove our items from its menu.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 55 (Total in Forum: 55) (Refresh) | FirstPrevNext |
|
 |
|
|
 |
|
|
Can we make subitems dynamically change, reading items from a text file, for example? And the list should be changed immediately after the items are read, not after restaring windows?
Also, is it possible to create shortcuts for menu items?
And can this code reside in a exe file, or it is a must to reside in a dll?
Thanks, Goran
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Hi MIke, We are implementing explorer like a virtual folder in windows vista and while creating "New" menu along with our customized context menu it is not loading default sub menu items("New Text document", "New Word Document" etc as shown in snapshot Windows_contextmenu.jpg (http://www.driveway.com/c6f1j0y1y3)) for the "New" menu of our customized explorer as shown in snapshot customeized_contextmenu.jpg ( http://www.driveway.com/h3q9s0x9q2 ) We are creating the shell folder using SHCreateShellFolderView() routine. any suggestions..... how to solve this...........
Aditya
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Michael,
I am working on a problem similar to that of Grigri's from back in 2003. I know that he has a Code Project article about his solution to the problem (called ShellPilot). As he discussed with you, he uses MFT_OWNERDRAW and stores some information in the WM_DRAWITEM handler. Unfortunately, he is then required to do all the handling of the drawing of the menu item himself. I am worried that this may not work properly with all the wide variety of UI customizations that can be done (Themes, skinning, changing default fonts, etc.).
Is there a way that I can, after receiving the WM_DRAWITEM and WM_MEASUREITEM messages, and storing some information, pass the message back to the default handler?
-- Eli Gibson
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
If you use the APIs to get the various system metrics and colors, your menus will look "right" no matter what the current theme is. There is no default behavior for WM_MEASUREITEM and WM_DRAWITEM. What would the system do? The app has an owner-draw object in its UI, but doesn't want to handle an owner-draw message?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
If context menu items depends on user selection(s), and one item must be removed for some reason, using menu index is not reliable anymore because GetCommandString and InvokeCommand won't display and start what is supposed. Your example is great if context menu items we add are always the same, but there is a way to use a command id like 101,102,103 and being able to display correct information in statusbar and start the right function if we remove command id 102. I'm not sure if my question is clear (english is not my first language).
I'm coding a context menu with MASM and your example is helping but I just can't find what is missing to do what I want. It's almost impossible that there is no way of doing it because WinZip, WinRAR are doing it in some way I can't figure.
Thanks for your help.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
It's ok I'll do the research as I should.
BTW, There is one mistake that make GetCommandString to failed on first menu item.
The first index should start with 1 like this...
InsertMenu ( hSubmenu, 1, MF_BYPOSITION, uID++, _T("&Notepad") ); InsertMenu ( hSubmenu, 2, MF_BYPOSITION, uID++, _T("&Internet Explorer") );
And wID from MENUITEMINFO should be uidFirstCmd like this...
mii.fMask = MIIM_SUBMENU | MIIM_STRING | MIIM_ID; mii.wID = uidFirstCmd; mii.hSubMenu = hSubmenu; mii.dwTypeData = _T("C&P Open With");
Finally you must increment uID by 1 as it was before replacing wID from uID to uidFirstCmd (it's a menu item anyway and it counts).
uID++
InsertMenuItem ( hmenu, uMenuIndex, TRUE, &mii );
This way you don't loose the ability to get statusbar text on any menu items that you add (GetCommandString).
-- modified at 23:30 Thursday 23rd March, 2006
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You claim that I have to return the number of added menu items. But what for? If I add *one* submenu item and give it the correct ID, I still added *only one* item.
Why is that? Well, the popup menu obviously has its own range of IDs (which you mention in you first reply below: "Re: All well and dandy, but what about dynamic submenus?"), so explorer can *at most* know that the current item is a submenu and kill the submenu, which will consequently remove the items, but isn't it ridiculous to assume that Explorer takes the same ID and then tries to remove items from the submenus - which require a different menu handle for one but would be easier to remove by just killing the submenu (popup menu)?! If you ever created a menu for a window, did you remove the menus after it one-by-one or did you just delete the menu to see all items vanish?
Are you sure that is the case? I will now try it without caring for the submenu IDs and will turn back to this thread with my results.
Cheers,
Oliver
-- modified at 5:44 Sunday 1st January, 2006
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Assarbad wrote: You claim that I have to return the number of added menu items. But what for?
Because that's what the documentation says to do. Ignore the docs at your own risk; the fact that it works now doesn't mean it'll work in future revs of the OS.
--Mike-- Visual C++ MVP  LINKS~! Ericahist | PimpFish | CP SearchBar v3.0 | C++ Forum FAQ Come quietly or there will be... trouble.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Actually it doesn't (at least mine, how about yours ): If successful, returns an HRESULT value that has its severity value set to SEVERITY_SUCCESS and its code value set to the offset of the largest command identifier that was assigned, plus one. For example, assume that idCmdFirst is set to 5 and you add three items to the menu with command identifiers of 5, 7, and 8. The return value should be MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1). Otherwise, it returns an OLE error value. It says "the menu", which, by my understanding of the English language (I am not a native speaker), refers to the HMENU parameter passed in to the function, not to any other menu item belonging to a completely different menu identified by a completely different menu handle. The question here is, why should Explorer even care about what (or how much) is inside the submenu, if the function DeleteMenu (which is likely to be used by Explorer; and if only implicitly) does the following according to the documentation: The DeleteMenu function deletes an item from the specified menu. If the menu item opens a menu or submenu, this function destroys the handle to the menu or submenu and frees the memory used by the menu or submenu.
For your convenience I have looked up the functions on MSDN, the documentation there seems to be the same as on my disk (although I still use the Server 2003 PSDK from Feb. 2003).
DeleteMenu IContextMenu::QueryContextMenu
//Added: By the way, some people asked why there is only a narrow range defined by idCmdFirst and idCmdLast. If it was like I said (has not been proven, yet) this would be the answer as well. Actually any other behavior would be quite odd, since the similar behavior on a file system would mean no less than that a certain file name can only be used once throughout the file system, no matter in which folder. I guess most of us would call this strange, no? 
Cheers,
Oliver
-- modified at 15:11 Sunday 1st January, 2006
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
This article is really helpful to me but I still have a problem with making a submenu.
I'm working on Windows Mobile 5.0 which doesn't support InsertMenuItem().
so I tried to do like this,
MENUITEMINFO menuItemInfo ; menuItemInfo.cbSize = sizeof (MENUITEMINFO); menuItemInfo.fMask = MIIM_ID | MIIM_SUBMENU ; menuItemInfo.wID = m_idc1 ; menuItemInfo.hSubMenu = hSubMenu;
InsertMenu ( hmenu, indexMenu ,MF_BYPOSITION | MF_STRING | MF_POPUP, (UINT)hSubMenu, _T("MENU")); SetMenuItemInfo ( hmenu ,indexMenu , TRUE , &menuItemInfo );
But...the context menu items I add are still duplicated on menu.
and, what about SEPARATOR which doesn't have a item ID
I hope you help me~~^^ Thanks a lot.
-- modified at 20:25 Tuesday 27th December, 2005
|
| Sign In·View Thread·PermaLink | 1.67/5 (3 votes) |
|
|
|
 |
|
|
I tried it the other way (with windows mobile 5.0).
I added a normal menu item and tried to attach it with a submenu but SetMenuInfo doesn't change the menu item to a popup menu:
InsertMenu(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, _T("My Menu")); mii.cbSize = sizeof(MENUITEMINFO); mii.hSubMenu = hMySubMenu; mii.fMask = MIIM_SUBMENU; SetMenuItemInfo(hmenu, indexMenu, true, &mii); Could anybody help?
|
| Sign In·View Thread·PermaLink | 5.00/5 (2 votes) |
|
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
|
I know I can add this command to right click context menu like the following:
; context_defrag.INF ; Adds Defrag to the right click context menu in Windows XP [version] signature="$CHICAGO$" [DefaultInstall] AddReg=AddMe [AddMe] HKCR,"Drive\Shell\Defrag\command",,,"DEFRAG.EXE %1"
But how do I add "scan with Norton AntiVirus" to right click context menu(s) in registry without having to re-install NAV?
Thank you
From an old guy
"I have no particular talent. I am merely inquisitive" - Albert Einstein
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi, Thanks for sharing this very useful information!
However I have a (newbie?) question: I had to convert the project to Visual Studio 7.0 and now it won't compile. I get stuff like this \OpenWithExtC2787: 'IContextMenu' : no GUID has been associated with this object \OpenWithCtxMenuExt.h(28) : error C2440: 'initializing' : cannot convert from 'DWORD_PTR' to 'const IID *' \OpenWithCtxMenuExt.h(29) : error C2078: too many initializers
Is there a simple fix, or is it just that there are wrong versions of everything?
If this is a stupid question, maybe someone could point me to a tutorial?
Any help is greatly appreciated!
/Hans
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|