使用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
相关产品推荐
相关产品推荐

