如何在指定IContextMenu::QueryContextMenu调用中移除「发送到」菜单项?
问题描述
需要从IContextMenu::QueryContextMenu返回的菜单中移除「发送到」菜单项,但该菜单项没有verb,无法通过verb匹配定位移除。要求仅针对特定的IContextMenu::QueryContextMenu调用排除,而非全局移除。曾尝试使用CMF_VERBSONLY标志,虽然能排除「发送到」,但会同时移除其他菜单,不符合需求。
解决方案
要精准移除「发送到」菜单项,可在QueryContextMenu调用完成后,遍历生成的菜单,通过菜单项文本或子菜单特征定位目标项并删除,具体实现如下:
核心思路
「发送到」菜单项有两个可靠特征:
- 文本对应系统资源字符串(可通过
SHLoadIndirectString获取,适配多语言) - 默认包含子菜单(可通过
GetSubMenu验证)
通过这两个特征组合判断,能避免误删其他菜单项,同时保证多语言环境下的兼容性。
实现步骤与代码
1. 定位并移除「发送到」的逻辑
在调用cm->QueryContextMenu成功后、显示菜单前,加入以下遍历删除逻辑:
// 获取系统默认的「发送到」文本 WCHAR sendToText[256] = {0}; SHLoadIndirectString(L"@shell32.dll,-31799", sendToText, ARRAYSIZE(sendToText), nullptr); int menuItemCount = GetMenuItemCount(menu); for (int i = 0; i < menuItemCount; ++i) { WCHAR menuText[256] = {0}; // 获取当前菜单项的文本 if (GetMenuString(menu, i, menuText, ARRAYSIZE(menuText), MF_BYPOSITION)) { // 匹配文本+验证子菜单,确保是目标项 if (_wcsicmp(menuText, sendToText) == 0 && GetSubMenu(menu, i) != nullptr) { RemoveMenu(menu, i, MF_BYPOSITION); break; // 找到后立即退出循环 } } }
2. 整合后的完整测试代码
将上述逻辑嵌入原测试代码,得到完整可运行版本:
#include <windows.h> #include <shlobj.h> #include <shlwapi.h> #pragma comment(lib, "shlwapi.lib") LPCWSTR path = L"c:\\users\\currentuser"; //LPCWSTR path = L"c:\\temp\\test.txt"; // 文件测试示例 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { const int buttonId = 1; const int buttonX = 10; const int buttonY = 10; switch (message) { case WM_CREATE: CreateWindow(L"BUTTON", L"显示右键菜单", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, buttonX, buttonY, 200, 30, hwnd, (HMENU)(long)buttonId, nullptr, nullptr); break; case WM_COMMAND: if (LOWORD(wParam) == buttonId) { IShellItem* item; SHCreateItemFromParsingName(path, NULL, IID_PPV_ARGS(&item)); if (item) { IContextMenu* cm; item->BindToHandler(nullptr, BHID_SFUIObject, IID_PPV_ARGS(&cm)); if (cm) { auto menu = CreatePopupMenu(); const int firstId = 1; HRESULT hr = cm->QueryContextMenu(menu, 0, firstId, 0x7FFF, CMF_NORMAL); if (SUCCEEDED(hr)) { // 移除「发送到」菜单项 WCHAR sendToText[256] = {0}; SHLoadIndirectString(L"@shell32.dll,-31799", sendToText, ARRAYSIZE(sendToText), nullptr); int menuItemCount = GetMenuItemCount(menu); for (int i = 0; i < menuItemCount; ++i) { WCHAR menuText[256] = {0}; if (GetMenuString(menu, i, menuText, ARRAYSIZE(menuText), MF_BYPOSITION)) { if (_wcsicmp(menuText, sendToText) == 0 && GetSubMenu(menu, i) != nullptr) { RemoveMenu(menu, i, MF_BYPOSITION); break; } } } POINT pt{ buttonX, buttonY }; ClientToScreen(hwnd, &pt); auto cmd = TrackPopupMenu(menu, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, nullptr); if (cmd) { CMINVOKECOMMANDINFO cmi{}; cmi.cbSize = sizeof(CMINVOKECOMMANDINFO); cmi.lpVerb = (LPSTR)MAKEINTRESOURCE(cmd - firstId); cmi.nShow = SW_SHOWNORMAL; cm->InvokeCommand(&cmi); } } cm->Release(); DestroyMenu(menu); } item->Release(); } } 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) { auto hmod = LoadLibrary(L"shell32.dll"); auto fileIconInit = (BOOL(WINAPI*)(BOOL))GetProcAddress(hmod, MAKEINTRESOURCEA(660)); if (fileIconInit) { fileIconInit(TRUE); } WNDCLASS wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = L"MyWindowClass"; RegisterClass(&wc); auto hwnd = CreateWindow(wc.lpszClassName, L"右键菜单测试", 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 0; }
关键说明
- 该方案仅针对当前
QueryContextMenu调用生成的菜单修改,不会影响系统全局右键菜单 - 使用系统资源字符串获取「发送到」文本,解决了多语言环境下的匹配问题
- 同时验证子菜单特征,避免误删同名普通菜单项
内容的提问来源于stack exchange,提问作者Joe J
相关产品推荐
相关产品推荐

