检测是否显示上下文菜单条或拦截关键事件

本文关键字:事件 是否 显示 上下文 菜单 检测 | 更新日期: 2023-09-27 18:36:02

通常,适用于 Winforms 控件的焦点概念指示哪个控件对象将接收某些事件,尤其是键盘事件。但是,如果窗体或其控件具有定义的ContextMenuStrip并且用户右键单击,则菜单将暂时截获按键事件,并阻止它们传递到以前Focused的控件。

请注意,在这种情况下,有问题的控件不会失去焦点(或LostFocus事件),而是处于某种伪无焦点状态:例如,TextBox插入符号将停止闪烁,但一旦菜单关闭,就会恢复正常行为。

使用派生自 TextBox 的自定义控件,并重载WndProcDefWinProcPreProcessMessage 方法来记录所有可能的 Window 消息,我看不到可以挂钩以检测此状态的消息。

调用 WinAPI 方法GetForegroundWindow()GetActiveWindow()Win32.GetFocus() 都返回相同的句柄,无论是否显示上下文菜单。

我的问题是:

自定义用户控件是否有任何方法可以检测窗体或其任何控件当前是否显示 ContextMenuStrip(理想情况下不必循环访问所有控件)?

检测是否显示上下文菜单条或拦截关键事件

在离开这个问题一段时间并做其他事情之后,我发现了一种相当简单的 WinAPI 方法来解决这个问题,它与 C# 配合得很好。

这里的魔力是WinAPI的SetWinEventHook函数:

// WINAPI Declarations
const uint EVENT_SYSTEM_MENUSTART = 0x0004;
const uint EVENT_SYSTEM_MENUEND = 0x0005;
const uint EVENT_SYSTEM_MENUPOPUPSTART = 0x0006;
const uint EVENT_SYSTEM_MENUPOPUPEND = 0x0007;
const uint WINEVENT_OUTOFCONTEXT = 0x0000
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll")]
static extern bool UnhookWinEvent(IntPtr hWinEventHook);
// Sample Usage - This version will subscribe to menu events on all processes and all threads:
void StartMonitoringMenus()
{
    // The lifetime of these two objects must be the same - we must not let the delegate 
    // get GC'd before calling UnhookWinEvent at the risk of crashing other processes.
    // This sample assumes class fields for these, but could also be static variables
    _menuEventCallback = new WinEventDelegate(MenuEventCallback);
    _menuEventHook = SetWinEventHook(
        EVENT_SYSTEM_MENUSTART,
        EVENT_SYSTEM_MENUPOPUPEND,
        IntPtr.Zero,
        _menuEventCallback,
        0,
        0,
        WINEVENT_OUTOFCONTEXT);
}
void StopMonitoringMenus()
{
    // Cleanup Logic
    UnhookWinEvent(_menuEventHook);
    _menuEventCallback = null;
}
void MenuEventCallback(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
    // Do something here like clear focus on poorly behaving hosted native controls
}

引发的特定事件顺序取决于所显示的菜单类型。

对于菜单条控件:

  1. 菜单开始
  2. 菜单弹出式启动
  3. 菜单弹出窗口
  4. 。菜单弹出窗口启动 - 当用户从一个菜单移动到另一个菜单而不进行选择
  5. 。菜单弹出窗口
  6. 菜单结束

对于上下文菜单条:

  1. 菜单弹出式启动
  2. 菜单弹出窗口