To display the menu I use the TrackPopupMenuEx
function. The menu I want to display I get directly from the shell extension. This is the menu for the item "New"
(HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\New
) which is displayed in the background context menu of the folder.
I was able to get the menu itself, but I can't display it correctly. When trying to display the menu, the following problems appear:
hNewMenu
(hNewMenu
is HMENU
obtained via QueryContextMenu
) to TrackPopupMenuEx
, then the item itself called "New"
is not displayed, but its child elements are shown directlyInvokeCommand
). However, they will work if you pass the menu to TrackPopupMenuEx
as GetSubMenu(hNewMenu, 0)
but in this case of course "New"
should not be visibleI tried to create a new menu hRootMenu
with a root item "New"
where the items from GetSubMenu(hNewMenu, 0)
could be placed as children. However, it did not work, the manually added parent item "New"
is still not displayed and instead the submenus from "New"
are displayed directly.
My question is, how can I display the item "New"
with its children inside and so that the menu items can be executed via IContextMenu::InvokeCommand
?
You can see my code below. To run this code and display the menu, you need to do the following:
path
variable value with the path to the any folderContext menu
button to display the context menu#include <Windows.h>
#include <ShlObj.h>
#include <sstream>
HINSTANCE hInst;
IContextMenu2* cm2;
IContextMenu3* cm3;
const wchar_t* path = L"C:\\Users\\Username\\Desktop\\folder";
bool UpdateContextMenu(LPCONTEXTMENU icm1, void** ppContextMenu)
{
*ppContextMenu = NULL;
if (icm1)
{ // since we got an IContextMenu interface we can
// now obtain the higher version interfaces via that
if (SUCCEEDED(icm1->QueryInterface(IID_IContextMenu3, ppContextMenu))) {
cm3 = (LPCONTEXTMENU3)*ppContextMenu;
}
else if (SUCCEEDED(icm1->QueryInterface(IID_IContextMenu2, ppContextMenu))) {
cm2 = (LPCONTEXTMENU2)*ppContextMenu;
}
if (*ppContextMenu) {
icm1->Release(); // we can now release version 1 interface,
// cause we got a higher one
}
else {
*ppContextMenu = icm1; // since no higher versions were found
} // redirect ppContextMenu to version 1 interface
}
else
return false; // something went wrong
return true; // success
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (cm3) {
LRESULT res;
if (SUCCEEDED(cm3->HandleMenuMsg2(message, wParam, lParam, &res))) {
return res;
}
}
else if (cm2) {
if (SUCCEEDED(cm2->HandleMenuMsg(message, wParam, lParam))) {
return 0;
}
}
switch (message) {
case WM_CREATE: {
CreateWindowA(
"BUTTON", "Context menu",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
100, 100, 100, 30,
hWnd, (HMENU)1, hInst, nullptr);
break;
}
case WM_COMMAND: {
if (LOWORD(wParam) == 1) {
LPITEMIDLIST pidl;
SFGAOF sfgao;
if (SUCCEEDED(SHParseDisplayName(path, NULL, &pidl, 0, &sfgao))) {
IShellFolder* psf;
LPCITEMIDLIST pidlChild;
if (SUCCEEDED(SHBindToParent(pidl, IID_IShellFolder, (void**)&psf, &pidlChild))) {
psf->Release();
}
}
IContextMenu* outPcm = nullptr;
CLSID clsid2;
CLSIDFromString(L"{D969A300-E7FF-11d0-A93B-00A0C90F2719}", &clsid2); // CLSID_NewMenu
const int SCRATCH_QCM_FIRST = 1;
const int SCRATCH_QCM_LAST = 0x7FFF;
IShellExtInit* shExtInit;
if (SUCCEEDED(CoCreateInstance(clsid2, NULL, CLSCTX_INPROC_SERVER, IID_IShellExtInit, (LPVOID*)&shExtInit))) {
if (SUCCEEDED(shExtInit->Initialize(pidl, NULL, NULL))) {
IContextMenu* cm = NULL;
if (SUCCEEDED(shExtInit->QueryInterface(IID_IContextMenu, (void**)&cm))) {
UpdateContextMenu(cm, (void**)&outPcm);
HMENU hNewMenu = CreatePopupMenu();
HMENU hRootMenu = CreatePopupMenu();
if (SUCCEEDED(outPcm->QueryContextMenu(hNewMenu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_NORMAL))) {
AppendMenu(hRootMenu, MF_STRING | MF_POPUP, (UINT_PTR)GetSubMenu(hNewMenu, 0), L"New");
int idCmd = TrackPopupMenuEx(hRootMenu, TPM_RETURNCMD, 100, 100, (HWND)hWnd, NULL);
CMINVOKECOMMANDINFO cmi = { 0 };
cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
cmi.lpVerb = (LPSTR)MAKEINTRESOURCE(idCmd - SCRATCH_QCM_FIRST);
cmi.nShow = SW_SHOWNORMAL;
HRESULT hr = outPcm->InvokeCommand(&cmi);
if (!SUCCEEDED(hr)) {
std::stringstream ss;
ss << "GetLastError() = " << GetLastError() << " hr = " << hr;
MessageBoxA(NULL, ss.str().c_str(), "", 0);
}
outPcm->Release();
cm2 = nullptr;
cm3 = nullptr;
}
}
}
}
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
hInst = hInstance;
WNDCLASSA wc = {};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "MYWINDOWCLASS";
RegisterClassA(&wc);
HWND hWnd = CreateWindowExA(
0, "MYWINDOWCLASS", "Test", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
nullptr, nullptr, hInstance, nullptr
);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
For whatever reason, the implementation expects that the WM_INITMENUPOPUP
message passed to HandleMenuMsg2
has the menu handle to the submenu it created in QueryContextMenu
([New >] [New]
). I assume the menu provided by IShellFolder
checks the menu item id so it knows which shell extension to forward the message to and the New menu is never the first menu item there so this issue does not happen in Explorer.
CreateNewMenuInstance(&g_cm); // Initialize with pidl
g_hmenu = CreatePopupMenu();
g_cm->QueryContextMenu(g_hmenu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, 0);
int cmd = TrackPopupMenuEx(g_hmenu, ...);
// if (cmd) ... InvokeCommand(...);
g_cm->Release(), g_cm = nullptr;
DestroyMenu(g_hmenu), g_hmenu = nullptr;
...
// LRESULT CALLBACK WndProc(...)
if (g_cm)
{
LRESULT res = 0;
if (message != WM_INITMENUPOPUP || (WPARAM)g_hmenu != wParam)
{
if (SUCCEEDED(SHForwardContextMenuMsg(g_cm, message, wParam, lParam, &res, true)))
return res;
}
}
...
return DefWindowProc(hWnd, message, wParam, lParam);
This workaround also fixes InvokeCommand
but the implementation expects you to provide the IContextMenu
with a site that implements IShellView
to get the full rename and shortcut wizard features.
I don't feel great about this hack, using the real IContextMenu
from IShellFolder
is much better if you want the full background menu.