如何正确显示并调用Shell扩展的「新建」上下文菜单?
如何正确显示Shell扩展的「新建」菜单并正常执行命令
我使用TrackPopupMenuEx函数显示从Shell扩展获取的菜单,目标是文件夹右键背景菜单中的「新建」项(对应注册表项HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\New)。目前已获取菜单,但存在以下问题:
- 直接将
QueryContextMenu得到的HMENU(hNewMenu)传入TrackPopupMenuEx时,「新建」项不显示,仅直接展示其子元素; - 显示的菜单项无法通过
IContextMenu::InvokeCommand执行,但传入GetSubMenu(hNewMenu, 0)时可执行,但此时「新建」项不可见。
我尝试创建新的根菜单hRootMenu,添加「新建」根项并将GetSubMenu(hNewMenu, 0)的子项作为其子菜单,但仍失败,手动添加的「新建」项不显示,仍直接展示子菜单。
请问如何既能显示包含子项的「新建」菜单,又能让菜单项通过IContextMenu::InvokeCommand正常执行?
代码运行说明
- 将
path变量替换为任意文件夹路径; - 点击「Context menu」按钮显示上下文菜单。
问题原因与解决方案
核心问题
NewMenuShell扩展的QueryContextMenu方法会直接将「新建」的子项填充到传入的菜单中,而非生成包含「新建」父项的完整菜单;- 手动添加父项后,命令ID的偏移计算错误,导致
InvokeCommand无法匹配正确的命令; - 菜单消息未正确传递给
IContextMenu2/IContextMenu3接口,影响菜单的正常交互。
修改要点
- 创建顶层菜单,添加「新建」父项,并将
QueryContextMenu生成的菜单作为其子菜单; - 调整
InvokeCommand的命令ID偏移,确保与QueryContextMenu的ID范围对应; - 在窗口过程中,确保子菜单的消息能被
IContextMenu2/IContextMenu3正确处理。
修改后的完整代码
#include <Windows.h> #include <ShlObj.h> #include <sstream> HINSTANCE hInst; IContextMenu2* cm2 = nullptr; IContextMenu3* cm3 = nullptr; const wchar_t* path = L"C:\\Users\\Username\\Desktop\\folder"; bool UpdateContextMenu(LPCONTEXTMENU icm1, void** ppContextMenu) { *ppContextMenu = nullptr; if (icm1) { // 尝试获取更高版本的IContextMenu接口 if (SUCCEEDED(icm1->QueryInterface(IID_IContextMenu3, ppContextMenu))) { cm3 = static_cast<IContextMenu3*>(*ppContextMenu); } else if (SUCCEEDED(icm1->QueryInterface(IID_IContextMenu2, ppContextMenu))) { cm2 = static_cast<IContextMenu2*>(*ppContextMenu); } if (*ppContextMenu) { icm1->Release(); // 释放旧接口 } else { *ppContextMenu = icm1; // 没有更高版本则保留原接口 } } else return false; return true; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // 优先让IContextMenu3/IContextMenu2处理菜单消息 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, reinterpret_cast<HMENU>(1), hInst, nullptr); break; } case WM_COMMAND: { if (LOWORD(wParam) == 1) { LPITEMIDLIST pidl = nullptr; SFGAOF sfgao; if (SUCCEEDED(SHParseDisplayName(path, NULL, &pidl, 0, &sfgao))) { // 保留pidl供Shell扩展初始化使用,不能提前释放 IContextMenu* outPcm = nullptr; const CLSID clsidNewMenu = {0xD969A300, 0xE7FF, 0x11d0, {0xA9, 0x3B, 0x00, 0xA0, 0xC9, 0x0F, 0x27, 0x19}}; const UINT SCRATCH_QCM_FIRST = 1000; // 使用较大的起始ID避免冲突 const UINT SCRATCH_QCM_LAST = 0x7FFF; IShellExtInit* shExtInit = nullptr; if (SUCCEEDED(CoCreateInstance(clsidNewMenu, NULL, CLSCTX_INPROC_SERVER, IID_IShellExtInit, reinterpret_cast<LPVOID*>(&shExtInit)))) { if (SUCCEEDED(shExtInit->Initialize(pidl, NULL, NULL))) { IContextMenu* cm = nullptr; if (SUCCEEDED(shExtInit->QueryInterface(IID_IContextMenu, reinterpret_cast<void**>(&cm)))) { UpdateContextMenu(cm, reinterpret_cast<void**>(&outPcm)); // 创建顶层菜单和「新建」子菜单容器 HMENU hRootMenu = CreatePopupMenu(); HMENU hNewSubMenu = CreatePopupMenu(); if (SUCCEEDED(outPcm->QueryContextMenu(hNewSubMenu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_NORMAL))) { // 将「新建」项添加到顶层菜单,并关联子菜单 AppendMenuW(hRootMenu, MF_POPUP | MF_STRING, reinterpret_cast<UINT_PTR>(hNewSubMenu), L"新建"); // 弹出菜单并获取选中的命令ID int idCmd = TrackPopupMenuEx(hRootMenu, TPM_RETURNCMD | TPM_RIGHTBUTTON, 100, 100, hWnd, NULL); if (idCmd >= SCRATCH_QCM_FIRST && idCmd <= SCRATCH_QCM_LAST) { CMINVOKECOMMANDINFO cmi = {0}; cmi.cbSize = sizeof(CMINVOKECOMMANDINFO); cmi.lpVerb = reinterpret_cast<LPSTR>(MAKEINTRESOURCE(idCmd - SCRATCH_QCM_FIRST)); cmi.nShow = SW_SHOWNORMAL; cmi.hwnd = hWnd; HRESULT hr = outPcm->InvokeCommand(&cmi); if (!SUCCEEDED(hr)) { std::stringstream ss; ss << "InvokeCommand失败: GetLastError() = " << GetLastError() << ", HRESULT = " << hr; MessageBoxA(NULL, ss.str().c_str(), "错误", MB_ICONERROR); } } DestroyMenu(hNewSubMenu); } DestroyMenu(hRootMenu); outPcm->Release(); } } shExtInit->Release(); } CoTaskMemFree(pidl); // 释放pidl } 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) { if (FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) { return 1; } hInst = hInstance; WNDCLASSA wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = "MYWINDOWCLASS"; wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1); if (!RegisterClassA(&wc)) { MessageBoxA(NULL, "窗口类注册失败", "错误", MB_ICONERROR); CoUninitialize(); return 1; } HWND hWnd = CreateWindowExA( 0, "MYWINDOWCLASS", "Shell菜单测试", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, nullptr, nullptr, hInstance, nullptr ); if (!hWnd) { MessageBoxA(NULL, "窗口创建失败", "错误", MB_ICONERROR); CoUninitialize(); return 1; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } CoUninitialize(); return static_cast<int>(msg.wParam); }
内容的提问来源于stack exchange,提问作者Joe J
相关产品推荐
相关产品推荐

