.Net Keyboard Hook Дополнительное событие KeyUp - PullRequest
1 голос
/ 01 февраля 2012

У меня есть бизнес-требование, чтобы в окнах сообщений пользователь не мог нажать клавишу ввода, чтобы принять опцию по умолчанию, а должен был нажать клавишу опции.например.При наличии 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 ]

1 голос
/ 03 февраля 2012

Добавьте эти строки кода где-нибудь в вашем классе (или в некотором статическом классе, который может использоваться другими классами):

[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() делает именно то, что вы хотите.

0 голосов
/ 06 февраля 2012

Спасибо М.Д.Уникорн. Метод PeekMessage и RemoveAllKeyMessages работает хорошо, за исключением незначительного изменения.

Я проводил дополнительные исследования по этой проблеме, и, очевидно, это известная проблема (даже указанная как проблема не решается в Microsoft Connect), когда 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), это должно быть решением для других, имеющих подобные проблемы.

0 голосов
/ 01 февраля 2012

На самом деле вы не можете сбросить события клавиатуры, но вы можете запретить получение события циклом сообщений потока.
Вы должны установить обработчик для WH_GETMESSAGE hook. 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;
}

Если вы столкнулись с какой-либо другой проблемой, дайте мне знать.

...