In the article, “Using the shell context menu”, I explained how to use the shell context menu in your applications. This article will explain how to add your own items to the shell context menu. This article builds on the previous article so be sure to read that article first.
After obtaining the IContextMenu for a particular file, you call QueryContextMenu() to obtain the actual context menu. You can add items to this popup menu to provide added features to your users. There are two basic ways to go about adding to the context menu:
Use the Windows API
Use a VCL TPopupMenu
Using the Windows API entails creating new menu items and inserting them into the popup menu. Alternatively, you can build the popup menu first, and then call QueryContextMenu() to merge the shell’s context menu with the menu you have created. Either way you will need a WndProc() to handle the menu items selected.
Most of you will probably want to take advantage of the VCL’s event mechanism so that is the technique I will describe in this article.
The first step is to add a TPopupMenu to your form and add menu items to the popup menu. After you have added the popup menu items, create event handlers for each item’s OnClick event.
Next you will merge the TPopupMenu items with the shell’s context menu. This step requires a bit of trickery. Here’s the code:
MENUITEMINFO mi;
char buff[80];
for (int i=PopupMenu1->Items->Count - 1;
i>-1;i--) {
ZeroMemory(&mi, sizeof(mi));
mi.dwTypeData = buff;
mi.cch = sizeof(buff);
mi.cbSize = 44;
mi.fMask =
MIIM_TYPE | MIIM_ID | MIIM_DATA;
DWORD result = GetMenuItemInfo(
PopupMenu1->Handle, i, true, &mi);
if (result) {
mi.wID = PopupMenu1->Items->
Items[i]->Command + 100;
InsertMenuItem(
hMenu, 0, true, &mi);
}
}
First I declare an instance of the MENUITEMINFO structure. Next I create a buffer to hold the menu item text. The for loop starts at the bottom of the menu and works backwards. I start at the bottom because each menu item in the TPopupMenu is inserted at the top of the shell’s context menu. In the for loop I fill in the required values for the MENUITEMINFO structure. Take special note of this line:
mi.cbSize = 44;
Normally you would use sizeof(mi) to assign the size of the structure to the cbSize member. For whatever reason, this causes the call to GetMenuItemInfo() to fail on some operating systems. By hard-coding the size to 44 bytes, the call will work on all operating systems. By the way, the VCL uses this technique as well. Normally I am reluctant to hard-code a value like this but it’s the only way to get the call to work in this case.
After obtaining the MENUITEMINFO for a particular menu item, I use this code:
mi.wID = PopupMenu1->Items->
Items[i]->Command + 100;
The VCL automatically assigns menu IDs to the items in a TPopupMenu. The shell context menu commands have values less than 100. By adding 100 to the menu ID generated by the VCL, I can determine whether a shell context item was clicked, or one of the items from the TPopupMenu. Finally, I insert each menu item into the context menu:
InsertMenuItem(hMenu, 0, true, &mi);
When TrackPopupMenu() returns (see the previous article), I check the return value and take appropriate action based on whether one of my menu items was clicked, or whether a shell item was clicked. The code looks like this:
int Cmd = TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_LEFTBUTTON |
TPM_RIGHTBUTTON | TPM_RETURNCMD,
pt.x, pt.y, 0, Handle, 0);
if (Cmd < 100 && Cmd != 0) {
CI.lpVerb = MAKEINTRESOURCE(Cmd - 1);
CI.lpParameters = "";
CI.lpDirectory = "";
CI.nShow = SW_SHOWNORMAL;
CM->InvokeCommand(&CI);
}
else
for (int i=0;i<PopupMenu1->Items->Count;i++) {
TMenuItem* menu =
PopupMenu1->Items->Items[i];
// Call its OnClick handler.
if (menu->Command == Cmd - 100)
menu->OnClick(this);
}
DestroyMenu(hMenu);
If the item clicked is one of the shell’s items I call InvokeCommand() to execute the item. If the item clicked is one of my TPopupMenu items, I find the item in the TPopupMenu and then call that item’s OnClick event handler. This allows me to use the VCL’s event handling mechanism to respond to the items I place on the context menu. This is much easier than using the API to add menu items, and fits nicely with the VCL model. See Listing A in the previous article to see the code in context.
Note that this allows you to add menu items to the shell context menu in your own programs. It does not, however, allow you to add items to the context menu shown by Explorer. Adding items to Explorer’s context menu involves writing a shell extension, and that is beyond the scope of this article.