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

使用Windows API和C#操控特定右键上下文菜单的问题求助

解决方案

一、精准定位Viewer对应的上下文菜单

由于所有上下文菜单和子菜单的类名均为#32768,直接枚举窗口无法区分,可通过钩子捕获菜单创建时序定位目标菜单:

  • 使用SetWindowsHookEx设置全局WH_CBT钩子,监听窗口创建事件
  • 在发送WM_CONTEXTMENU给Viewer前,标记当前是Viewer触发的右键操作;钩子捕获到#32768类窗口创建时,结合标记识别目标菜单

示例代码:

private static IntPtr hookHandle = IntPtr.Zero;
private static bool isViewerRightClickTriggered = false;
private static IntPtr currentMenuHandle = IntPtr.Zero;

private const int HCBT_CREATEWND = 3;

[StructLayout(LayoutKind.Sequential)]
private struct CBT_CREATEWND
{
    public IntPtr hwndInsertAfter;
    public IntPtr hWnd;
    public IntPtr hInstance;
    public uint style;
    public int x;
    public int y;
    public int cx;
    public int cy;
    public IntPtr lpszName;
    public IntPtr lpszClass;
    public uint dwExStyle;
}

private static IntPtr CbtHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0 && nCode == HCBT_CREATEWND)
    {
        var createStruct = (CBT_CREATEWND)Marshal.PtrToStructure(lParam, typeof(CBT_CREATEWND));
        var className = new StringBuilder(256);
        GetClassName(createStruct.hWnd, className, className.Capacity);
        
        if (className.ToString() == "#32768" && isViewerRightClickTriggered)
        {
            currentMenuHandle = createStruct.hWnd;
            isViewerRightClickTriggered = false;
            // 直接在此修改菜单
            ModifyTargetMenu(currentMenuHandle);
        }
    }
    return CallNextHookEx(hookHandle, nCode, wParam, lParam);
}

// 触发Viewer右键时调用
private void TriggerViewerContextMenu(IntPtr viewerHandle)
{
    isViewerRightClickTriggered = true;
    SendMessage(viewerHandle, WM_CONTEXTMENU, viewerHandle, (IntPtr)((Cursor.Position.Y << 16) | Cursor.Position.X));
}

二、在菜单显示前完成修改

通过WH_CBT钩子可在菜单窗口创建(显示前)获取句柄,此时直接调用Windows API修改菜单即可:

private void ModifyTargetMenu(IntPtr menuWindowHandle)
{
    // 获取弹出菜单句柄
    IntPtr hMenu = GetSubMenu(menuWindowHandle, 0);
    if (hMenu == IntPtr.Zero) return;

    // 插入自定义菜单项
    MENUITEMINFO menuItem = new MENUITEMINFO();
    menuItem.cbSize = Marshal.SizeOf(typeof(MENUITEMINFO));
    menuItem.fMask = MIIM_STRING | MIIM_ID;
    menuItem.wID = 0x1001;
    menuItem.dwTypeData = "自定义扩展项";
    InsertMenuItem(hMenu, 0, true, ref menuItem);
}

三、替代方案:拦截菜单初始化消息

若全局钩子过于繁琐,可设置WH_CALLWNDPROC钩子监听目标应用的WM_INITMENUPOPUP消息(菜单显示前触发):

  • 捕获消息时,通过鼠标位置判断是否属于Viewer区域
  • 直接修改消息参数中的菜单句柄

示例代码片段:

private const int WM_INITMENUPOPUP = 0x0117;

[StructLayout(LayoutKind.Sequential)]
private struct CWPSTRUCT
{
    public IntPtr lParam;
    public IntPtr wParam;
    public uint message;
    public IntPtr hwnd;
}

private static IntPtr CallWndProcHook(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0)
    {
        var msg = (CWPSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPSTRUCT));
        if (msg.message == WM_INITMENUPOPUP)
        {
            IntPtr menuHandle = msg.wParam;
            Point mousePos = Cursor.Position;
            if (IsPointInViewer(mousePos))
            {
                ModifyTargetMenu(menuHandle);
            }
        }
    }
    return CallNextHookEx(hookHandle, nCode, wParam, lParam);
}

private static bool IsPointInViewer(Point screenPos)
{
    Point clientPos = PointToClient(viewerHandle, screenPos);
    RECT rect = new RECT();
    GetClientRect(viewerHandle, out rect);
    return clientPos.X >= rect.left && clientPos.X <= rect.right && clientPos.Y >= rect.top && clientPos.Y <= rect.bottom;
}

核心注意事项

  • 上下文菜单是临时窗口,每次右键都会重新创建,禁止复用句柄,必须每次触发时重新捕获
  • 钩子需要注入目标进程,程序需以管理员身份运行避免权限不足
  • 操作菜单时需在钩子回调线程中执行,避免跨线程调用异常

内容的提问来源于stack exchange,提问作者Ahmad Eltobshy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 12:15:42