挂钩键盘复制输入(c# / xna)
我在我的 XNA 项目中使用键盘钩子(任何使用 XNA 的人都知道内置键盘类对于“文本框”样式的输入是多么无用。
在引入表单之前,它在 XNA 项目中使用效果很好。如果我尝试在项目中实现任何表单,无论是自定义表单,还是只是一个 OpenFileDialog,表单文本框中的任何按键都会被加倍,使得打字几乎不可能
有人知道如何停止该消息 。也许通过两次表格?当我收到消息时丢弃该消息?也许有更好的解决方案?或者可能只是无法完成
任何帮助
编辑:
下面是我正在使用的键盘挂钩代码,对于任何寻找 XNA 键盘挂钩的人来说,它可能很熟悉,因为它似乎在我寻找它的任何地方都会出现。
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms; // This class exposes WinForms-style key events.
namespace FatLib.Controls
{
class KeyboardHookInput : IDisposable
{
private string _buffer = "";
private bool _backSpace = false;
private bool _enterKey = false; // I added this to the original code
public string Buffer
{
get { return _buffer; }
}
public bool BackSpace
{
get
{
return _backSpace;
}
}
public bool EnterKey
{
get
{
return _enterKey;
}
}
public void Reset()
{
_buffer = "";
_backSpace = false;
_enterKey = false;
}
public enum HookId
{
// Types of hook that can be installed using the SetWindwsHookEx function.
WH_CALLWNDPROC = 4,
WH_CALLWNDPROCRET = 12,
WH_CBT = 5,
WH_DEBUG = 9,
WH_FOREGROUNDIDLE = 11,
WH_GETMESSAGE = 3,
WH_HARDWARE = 8,
WH_JOURNALPLAYBACK = 1,
WH_JOURNALRECORD = 0,
WH_KEYBOARD = 2,
WH_KEYBOARD_LL = 13,
WH_MAX = 11,
WH_MAXHOOK = WH_MAX,
WH_MIN = -1,
WH_MINHOOK = WH_MIN,
WH_MOUSE_LL = 14,
WH_MSGFILTER = -1,
WH_SHELL = 10,
WH_SYSMSGFILTER = 6,
};
public enum WindowMessage
{
// Window message types.
WM_KEYDOWN = 0x100,
WM_KEYUP = 0x101,
WM_CHAR = 0x102,
};
// A delegate used to create a hook callback.
public delegate int GetMsgProc(int nCode, int wParam, ref Message msg);
/// <summary>
/// Install an application-defined hook procedure into a hook chain.
/// </summary>
/// <param name="idHook">Specifies the type of hook procedure to be installed.</param>
/// <param name="lpfn">Pointer to the hook procedure.</param>
/// <param name="hmod">Handle to the DLL containing the hook procedure pointed to by the lpfn parameter.</param>
/// <param name="dwThreadId">Specifies the identifier of the thread with which the hook procedure is to be associated.</param>
/// <returns>If the function succeeds, the return value is the handle to the hook procedure. Otherwise returns 0.</returns>
[DllImport("user32.dll", EntryPoint = "SetWindowsHookExA")]
public static extern IntPtr SetWindowsHookEx(HookId idHook, GetMsgProc lpfn, IntPtr hmod, int dwThreadId);
/// <summary>
/// Removes a hook procedure installed in a hook chain by the SetWindowsHookEx function.
/// </summary>
/// <param name="hHook">Handle to the hook to be removed. This parameter is a hook handle obtained by a previous call to SetWindowsHookEx.</param>
/// <returns>If the function fails, the return value is zero. To get extended error information, call GetLastError.</returns>
[DllImport("user32.dll")]
public static extern int UnhookWindowsHookEx(IntPtr hHook);
/// <summary>
/// Passes the hook information to the next hook procedure in the current hook chain.
/// </summary>
/// <param name="hHook">Ignored.</param>
/// <param name="ncode">Specifies the hook code passed to the current hook procedure.</param>
/// <param name="wParam">Specifies the wParam value passed to the current hook procedure.</param>
/// <param name="lParam">Specifies the lParam value passed to the current hook procedure.</param>
/// <returns>This value is returned by the next hook procedure in the chain.</returns>
[DllImport("user32.dll")]
public static extern int CallNextHookEx(int hHook, int ncode, int wParam, ref Message lParam);
/// <summary>
/// Translates virtual-key messages into character messages.
/// </summary>
/// <param name="lpMsg">Pointer to an Message structure that contains message information retrieved from the calling thread's message queue.</param>
/// <returns>If the message is translated (that is, a character message is posted to the thread's message queue), the return value is true.</returns>
[DllImport("user32.dll")]
public static extern bool TranslateMessage(ref Message lpMsg);
/// <summary>
/// Retrieves the thread identifier of the calling thread.
/// </summary>
/// <returns>The thread identifier of the calling thread.</returns>
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
// Handle for the created hook.
private readonly IntPtr HookHandle;
private readonly GetMsgProc ProcessMessagesCallback;
public KeyboardHookInput()
{
// Create the delegate callback:
this.ProcessMessagesCallback = new GetMsgProc(ProcessMessages);
// Create the keyboard hook:
this.HookHandle = SetWindowsHookEx(HookId.WH_KEYBOARD, this.ProcessMessagesCallback, IntPtr.Zero, GetCurrentThreadId());
}
public void Dispose()
{
// Remove the hook.
if (HookHandle != IntPtr.Zero) UnhookWindowsHookEx(HookHandle);
}
// comments found in this region are all from the original author: Darg.
private int ProcessMessages(int nCode, int wParam, ref Message msg)
{
// Check if we must process this message (and whether it has been retrieved via GetMessage):
if (nCode == 0 && wParam == 1)
{
// We need character input, so use TranslateMessage to generate WM_CHAR messages.
TranslateMessage(ref msg);
// If it's one of the keyboard-related messages, raise an event for it:
switch ((WindowMessage)msg.Msg)
{
case WindowMessage.WM_CHAR:
this.OnKeyPress(new KeyPressEventArgs((char)msg.WParam));
break;
case WindowMessage.WM_KEYDOWN:
this.OnKeyDown(new KeyEventArgs((Keys)msg.WParam));
break;
case WindowMessage.WM_KEYUP:
this.OnKeyUp(new KeyEventArgs((Keys)msg.WParam));
break;
}
}
// Call next hook in chain:
return CallNextHookEx(0, nCode, wParam, ref msg);
}
public event KeyEventHandler KeyUp;
protected virtual void OnKeyUp(KeyEventArgs e)
{
if (KeyUp != null) KeyUp(this, e);
}
public event KeyEventHandler KeyDown;
protected virtual void OnKeyDown(KeyEventArgs e)
{
if (KeyDown != null) KeyDown(this, e);
}
public event KeyPressEventHandler KeyPress;
protected virtual void OnKeyPress(KeyPressEventArgs e)
{
if (KeyPress != null) KeyPress(this, e);
if (e.KeyChar.GetHashCode().ToString() == "524296")
{
_backSpace = true;
}
else if (e.KeyChar == (char)Keys.Enter)
{
_enterKey = true;
}
else
{
_buffer += e.KeyChar;
}
}
}
}
I'm using a Keyboard hook in my XNA project (anyone who uses XNA knows how useless the built in keyboard class is for "text box" style typed input.
It works just fine for use in XNA projects, until forms are introduced. If I try to implement any form in the project, be it a custom form, or even just an OpenFileDialog, any key press inside a text box of the form is doubled, making typing just about impossible.
Does anyone know how I can stop the message from reaching the forms twice? perhaps by discarding the message when I get it? Perhaps there is a better solution? or perhaps its just something that cant be done.
Any help is appreciated.
EDIT:
Below is the keyboard hooking code I'm using, It may be familiar to anyone who's gone looking for XNA keyboard hooking as it seems to crop up wherever I looked for it.
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms; // This class exposes WinForms-style key events.
namespace FatLib.Controls
{
class KeyboardHookInput : IDisposable
{
private string _buffer = "";
private bool _backSpace = false;
private bool _enterKey = false; // I added this to the original code
public string Buffer
{
get { return _buffer; }
}
public bool BackSpace
{
get
{
return _backSpace;
}
}
public bool EnterKey
{
get
{
return _enterKey;
}
}
public void Reset()
{
_buffer = "";
_backSpace = false;
_enterKey = false;
}
public enum HookId
{
// Types of hook that can be installed using the SetWindwsHookEx function.
WH_CALLWNDPROC = 4,
WH_CALLWNDPROCRET = 12,
WH_CBT = 5,
WH_DEBUG = 9,
WH_FOREGROUNDIDLE = 11,
WH_GETMESSAGE = 3,
WH_HARDWARE = 8,
WH_JOURNALPLAYBACK = 1,
WH_JOURNALRECORD = 0,
WH_KEYBOARD = 2,
WH_KEYBOARD_LL = 13,
WH_MAX = 11,
WH_MAXHOOK = WH_MAX,
WH_MIN = -1,
WH_MINHOOK = WH_MIN,
WH_MOUSE_LL = 14,
WH_MSGFILTER = -1,
WH_SHELL = 10,
WH_SYSMSGFILTER = 6,
};
public enum WindowMessage
{
// Window message types.
WM_KEYDOWN = 0x100,
WM_KEYUP = 0x101,
WM_CHAR = 0x102,
};
// A delegate used to create a hook callback.
public delegate int GetMsgProc(int nCode, int wParam, ref Message msg);
/// <summary>
/// Install an application-defined hook procedure into a hook chain.
/// </summary>
/// <param name="idHook">Specifies the type of hook procedure to be installed.</param>
/// <param name="lpfn">Pointer to the hook procedure.</param>
/// <param name="hmod">Handle to the DLL containing the hook procedure pointed to by the lpfn parameter.</param>
/// <param name="dwThreadId">Specifies the identifier of the thread with which the hook procedure is to be associated.</param>
/// <returns>If the function succeeds, the return value is the handle to the hook procedure. Otherwise returns 0.</returns>
[DllImport("user32.dll", EntryPoint = "SetWindowsHookExA")]
public static extern IntPtr SetWindowsHookEx(HookId idHook, GetMsgProc lpfn, IntPtr hmod, int dwThreadId);
/// <summary>
/// Removes a hook procedure installed in a hook chain by the SetWindowsHookEx function.
/// </summary>
/// <param name="hHook">Handle to the hook to be removed. This parameter is a hook handle obtained by a previous call to SetWindowsHookEx.</param>
/// <returns>If the function fails, the return value is zero. To get extended error information, call GetLastError.</returns>
[DllImport("user32.dll")]
public static extern int UnhookWindowsHookEx(IntPtr hHook);
/// <summary>
/// Passes the hook information to the next hook procedure in the current hook chain.
/// </summary>
/// <param name="hHook">Ignored.</param>
/// <param name="ncode">Specifies the hook code passed to the current hook procedure.</param>
/// <param name="wParam">Specifies the wParam value passed to the current hook procedure.</param>
/// <param name="lParam">Specifies the lParam value passed to the current hook procedure.</param>
/// <returns>This value is returned by the next hook procedure in the chain.</returns>
[DllImport("user32.dll")]
public static extern int CallNextHookEx(int hHook, int ncode, int wParam, ref Message lParam);
/// <summary>
/// Translates virtual-key messages into character messages.
/// </summary>
/// <param name="lpMsg">Pointer to an Message structure that contains message information retrieved from the calling thread's message queue.</param>
/// <returns>If the message is translated (that is, a character message is posted to the thread's message queue), the return value is true.</returns>
[DllImport("user32.dll")]
public static extern bool TranslateMessage(ref Message lpMsg);
/// <summary>
/// Retrieves the thread identifier of the calling thread.
/// </summary>
/// <returns>The thread identifier of the calling thread.</returns>
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
// Handle for the created hook.
private readonly IntPtr HookHandle;
private readonly GetMsgProc ProcessMessagesCallback;
public KeyboardHookInput()
{
// Create the delegate callback:
this.ProcessMessagesCallback = new GetMsgProc(ProcessMessages);
// Create the keyboard hook:
this.HookHandle = SetWindowsHookEx(HookId.WH_KEYBOARD, this.ProcessMessagesCallback, IntPtr.Zero, GetCurrentThreadId());
}
public void Dispose()
{
// Remove the hook.
if (HookHandle != IntPtr.Zero) UnhookWindowsHookEx(HookHandle);
}
// comments found in this region are all from the original author: Darg.
private int ProcessMessages(int nCode, int wParam, ref Message msg)
{
// Check if we must process this message (and whether it has been retrieved via GetMessage):
if (nCode == 0 && wParam == 1)
{
// We need character input, so use TranslateMessage to generate WM_CHAR messages.
TranslateMessage(ref msg);
// If it's one of the keyboard-related messages, raise an event for it:
switch ((WindowMessage)msg.Msg)
{
case WindowMessage.WM_CHAR:
this.OnKeyPress(new KeyPressEventArgs((char)msg.WParam));
break;
case WindowMessage.WM_KEYDOWN:
this.OnKeyDown(new KeyEventArgs((Keys)msg.WParam));
break;
case WindowMessage.WM_KEYUP:
this.OnKeyUp(new KeyEventArgs((Keys)msg.WParam));
break;
}
}
// Call next hook in chain:
return CallNextHookEx(0, nCode, wParam, ref msg);
}
public event KeyEventHandler KeyUp;
protected virtual void OnKeyUp(KeyEventArgs e)
{
if (KeyUp != null) KeyUp(this, e);
}
public event KeyEventHandler KeyDown;
protected virtual void OnKeyDown(KeyEventArgs e)
{
if (KeyDown != null) KeyDown(this, e);
}
public event KeyPressEventHandler KeyPress;
protected virtual void OnKeyPress(KeyPressEventArgs e)
{
if (KeyPress != null) KeyPress(this, e);
if (e.KeyChar.GetHashCode().ToString() == "524296")
{
_backSpace = true;
}
else if (e.KeyChar == (char)Keys.Enter)
{
_enterKey = true;
}
else
{
_buffer += e.KeyChar;
}
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
Windows Hook 是获取所有按键的一种非常糟糕的方式,并且绝对应该是最后的手段。
尝试在您的应用程序中安装消息过滤器,它可以监视发送到您的应用程序的所有键盘消息(WM_KEYPRESS、KEYUP、KEYDOWN 等),而不会干扰其他应用程序。如果您愿意,过滤器还允许您阻止任何消息到达应用程序中的任何表单。
A Windows Hook is a pretty nasty way of getting at all the keypresses, and should be an absolute last resort.
Try installing a Message Filter into your application, which can watch for all the keyboard messages that are sent to your application (WM_KEYPRESS, KEYUP, KEYDOWN, etc) without interfering with other applications. A filter will also allow you to stop any messages reaching any forms in your application if you wish.
由于 XNA 中必须有一个表单(
GraphicsDevice
需要它),因此您将有 2 个选项,具体取决于您的库的用户是否创建表单(winforms 项目,使用 XNA 进行渲染),或者表单是否是作为创建的结果而创建的一个普通的 XNA 游戏。在 winforms 情况下,您可以只处理密钥事件,工作完成。
在标准 XNA 游戏案例中,您可以让用户传递图形设备创建的窗口的句柄,然后在调用
OnKey...
方法之前,检查窗口是否具有焦点。在这两种情况下,您都可以选择第二个选项,以使其更简单。您的库的用户将传入窗口句柄以在
KeyboardHookInput
的构造函数中接收/缓冲按键操作。然后,您首先检查窗口是否具有焦点(请参见此处:如何判断窗口是否具有焦点?(Win32 API))。Since there has to be a form in XNA (the
GraphicsDevice
requires it) you're going to have 2 options depending on whether the user of your library creates the form (winforms project with XNA for rendering), or whether the form is created as a result of creating a normal XNA game.In the winforms case, you can just handle the key events, job done.
In the standard XNA game case, you can get the user to pass the handle of the window that the graphics device created, then before calling your
OnKey...
methods, check that the window has focus.You could go with the 2nd option in both cases to make it simpler. The user of your library would pass in the handle of the window to receive/buffer key presses in the constructor of
KeyboardHookInput
. Then you'd check that the window has focus first (see here: How can I tell if a Window has focus? (Win32 API)).