.Net Keyboard Hook Extra KeyUp Event

时间:2012-02-01 05:57:56

标签: .net marshalling messagebox keyboard-hook

我有一个业务要求,对于消息框,用户不能按回车键接受默认选项,但必须按下该选项的键。例如。给定带有选项是/否的MessageBox,用户必须按Y或N键。现在我已经使用键盘钩子实现了以下功能,但是当代码返回时,KeyUp事件也会返回到调用代码。

所以问题是:如何在返回主叫代码之前刷新所有键盘事件?

我已经删除了锅炉铭牌代码,但如果您需要,请提供建议。

主叫代码:

    private static ResultMsgBox MsgResultBaseNoEnter(string msg, string caption, uint options)
    {
        ResultMsgBox res;
        _hookID = SetHook(_proc);
        try
        {
            res = MessageBox(GetForegroundWindow(), msg, caption, options);
        }
        finally
        {
            UnhookWindowsHookEx(_hookID);
        }
        return res;
    }

和胡克代码:

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            int vkCode = Marshal.ReadInt32(lParam);
            if (vkCode == VK_RETURN)
                return (IntPtr)(-1);
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

3 个答案:

答案 0 :(得分:1)

在您的类中的某个位置添加这些代码行(或者在其他类可以使用的某个静态类中):

[StructLayout(LayoutKind.Sequential)]
public class MSG
{
    public IntPtr hwnd;
    public uint message;
    public IntPtr wParam;
    public IntPtr lParam;
    public uint time;
    int x;
    int y;
}

[DllImport("user32")]
public static extern bool PeekMessage([Out]MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, int wRemoveMsg);

/// <summary>
/// Examines the message queue for key messages.
/// </summary>
/// <param name="remove">If this parameter is true, the returned message is also removed from the queue.</param>
/// <returns>Returns the next available key message, or null if there is no key message available.</returns>
public static MSG PeekKeyMessage(bool remove)
{
    MSG msg = new MSG();
    if (PeekMessage(msg, IntPtr.Zero, 0x0100 /*WM_KEYFIRST*/, 0x0109 /*WM_KEYLAST*/, remove ? 1 : 0))
        return msg;
    return null;
}

public static void RemoveAllKeyMessages()
{
    while (PeekKeyMessage(true) != null) ; // Empty body. Every call to the method removes one key message.
}

调用RemoveAllKeyMessages()完全符合您的要求。

答案 1 :(得分:0)

实际上你不能刷新键盘事件,但你可以阻止线程的消息循环接收事件。
您应该为WH_GETMESSAGE挂钩安装处理程序。钩子过程的lParam是指向MSG结构的指针。检查结构后,您可以更改它以避免将消息传递给调用消息处理器。您应该将消息更改为WM_NULL .NET中的实际过程有点长,需要单独的文章。但简单来说,这是如何:

在项目的新C#文件中完全按原样复制此类:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Unicorn
{
    public static class HookManager
    {
        #region Fields

        private delegate int HookDelegate(int ncode, IntPtr wParam, IntPtr lParam);
        private static HookDelegate getMessageHookProc;
        private static IntPtr getMessageHookHandle;
        private static List<EventHandler<GetMessageHookEventArgs>> getMessageHandlers =
            new List<EventHandler<GetMessageHookEventArgs>>();

        #endregion
        #region Private Methods - Installation and Uninstallation

        private static void InstallGetMessageHook()
        {
            if (getMessageHookProc != null)
                return;
            getMessageHookProc = new HookDelegate(GetMessageHookProc);
            getMessageHookHandle = SetWindowsHookEx(WH_GETMESSAGE, getMessageHookProc, 0, GetCurrentThreadId());
        }

        private static void UninstallGetMessageHook()
        {
            if (getMessageHookProc == null)
                return;
            UnhookWindowsHookEx(getMessageHookHandle);
            getMessageHookHandle = IntPtr.Zero;
            getMessageHookProc = null;
        }

        #endregion
        #region Public Methods - Add and Remove Handlers

        public static void AddGetMessageHookHandler(EventHandler<GetMessageHookEventArgs> handler)
        {
            if (getMessageHandlers.Contains(handler))
                return;
            getMessageHandlers.Add(handler);
            if (getMessageHandlers.Count == 1)
                InstallGetMessageHook();
        }

        public static void RemoveGetMessageHookHandler(EventHandler<GetMessageHookEventArgs> handler)
        {
            getMessageHandlers.Remove(handler);
            if (getMessageHandlers.Count == 0)
                UninstallGetMessageHook();
        }

        #endregion
        #region Private Methods - Hook Procedures

        [DebuggerStepThrough]
        private static int GetMessageHookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            if (code == 0) // HC_ACTION
            {
                MSG msg = new MSG();
                Marshal.PtrToStructure(lParam, msg);
                GetMessageHookEventArgs e = new GetMessageHookEventArgs()
                {
                    HWnd = msg.hwnd,
                    Msg = msg.message,
                    WParam = msg.wParam,
                    LParam = msg.lParam,
                    MessageRemoved = (int)wParam == 1,
                    ShouldApplyChanges = false
                };

                foreach (var handler in getMessageHandlers.ToArray())
                {
                    handler(null, e);
                    if (e.ShouldApplyChanges)
                    {
                        msg.hwnd = e.HWnd;
                        msg.message = e.Msg;
                        msg.wParam = e.WParam;
                        msg.lParam = e.LParam;
                        Marshal.StructureToPtr(msg, (IntPtr)lParam, false);
                        e.ShouldApplyChanges = false;
                    }
                }
            }

            return CallNextHookEx(getMessageHookHandle, code, wParam, lParam);
        }

        #endregion
        #region Win32 stuff

        private const int WH_KEYBOARD = 2;
        private const int WH_GETMESSAGE = 3;
        private const int WH_CALLWNDPROC = 4;
        private const int WH_MOUSE = 7;
        private const int WH_CALLWNDPROCRET = 12;

        [StructLayout(LayoutKind.Sequential)]
        public class MSG
        {
            public IntPtr hwnd;
            public uint message;
            public IntPtr wParam;
            public IntPtr lParam;
            public uint time;
            int x;
            int y;
        }


        [DllImport("USER32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SetWindowsHookEx(int idHook, HookDelegate lpfn, int hMod, int dwThreadId);

        [DllImport("USER32.dll", CharSet = CharSet.Auto)]
        private static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("USER32.dll", CharSet = CharSet.Auto)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
        private static extern int GetCurrentThreadId();
        #endregion
    }

    #region EventArgs

    public class GetMessageHookEventArgs : EventArgs
    {
        public uint Msg { get; set; }
        public IntPtr HWnd { get; set; }
        public IntPtr WParam { get; set; }
        public IntPtr LParam { get; set; }

        public bool MessageRemoved { get; set; }
        public bool ShouldApplyChanges { get; set; }
    }

    #endregion
}

这是一个帮助类,可以满足您的所有需求。我的实际类是一个更长的时间,可以处理更多的钩子类型,但我清除了代码,使其变小。

在此之后,您的代码应如下所示:

private static void GetMessageProcHook(object sender, Unicorn.GetMessageHookEventArgs e)
{
    if (e.Msg == 0x100 && (Keys)e.WParam == Keys.Return) // WM_KEYDOWN
    {
        // swallow the message
        e.Msg = 0; // WM_NULL
        e.WParam = IntPtr.Zero;
        e.LParam = IntPtr.Zero;
        e.ShouldApplyChanges = true; // This will tell the HookManager to copy the changes back.
    }
}

private static ResultMsgBox MsgResultBaseNoEnter(string msg, string caption, uint options)
{
    ResultMsgBox res;
    Unicorn.HookManager.AddGetMessageHookHandler(GetMessageProcHook);
    try
    {
        res = MessageBox(GetForegroundWindow(), msg, caption, options);
    }
    finally
    {
        Unicorn.HookManager.RemoveGetMessageHookHandler(GetMessageProcHook);
    }
    return res;
}

如果您遇到任何其他问题,请与我们联系。

答案 2 :(得分:0)

感谢MD.Unicorn。 PeekMessage和RemoveAllKeyMessages方法运行良好,除了一个小的改动。

我一直在对这个问题进行更多的研究,显然这是一个已知的问题(甚至在Microsoft connect中列为Will not Fix问题),MessageBox接受KeyDown事件的输入选项,然后关闭窗口,然后返回的窗口将在稍后的时间收到KeyUp事件。

据我所知,这个KeyUp事件将在未来发生,而不是立即发生。 (RemoveAllKeyMessages本身并没有解决问题。)我只是调整了方法,以便按如下方式轮询它。我已重命名该方法以指示它是MessageBox问题的自定义用法。

    public static void RemoveMessageBoxKeyMessages()
    {
        //Loop until the MessageBox KeyUp event fires
        var timeOut = DateTime.Now;
        while (PeekKeyMessage(false) == null && DateTime.Now.Subtract(timeOut).TotalSeconds < 1)
           System.Threading.Thread.Sleep(100);

        while (PeekKeyMessage(true) != null) ; // Empty body. Every call to the method removes one key message.
    }

除非有明显的缺陷(除非消息框没有发送KeyUp事件),否则这应该是其他有类似问题的解决方案。