检测是否显示ContextMenuStrip或拦截关键事件

时间:2016-02-24 21:53:07

标签: c# winforms winapi

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

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

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

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

我的问题是:

自定义用户控件是否有任何方法可以检测表单或其任何控件当前是否显示ContextMenuStrip(理想情况下无需迭代所有控件)?

1 个答案:

答案 0 :(得分:0)

在离开这个问题一段时间并处理其他事情之后,我发现了一种适度的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
}

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

对于MenuStrip控件:

  1. MENUSTART
  2. MENUPOPUPSTART
  3. MENUPOPUPEND
  4. ... MENUPOPUPSTART - 当用户从菜单移动到菜单而不进行选择时
  5. ...... MENUPOPUPEND
  6. MENUEND
  7. 对于ContextMenuStrips:

    1. MENUPOPUPSTART
    2. MENUPOPUPEND