You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在指定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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 03:15:07