我有一个业务要求,对于消息框,用户不能按回车键接受默认选项,但必须按下该选项的键。例如。给定带有选项是/否的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);
}
答案 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事件),否则这应该是其他有类似问题的解决方案。