XNA – 键盘文本输入

好的,基本上我希望能够检索键盘文本。 就像将文本输入文本字段或其他内容一样。 我只是为Windows编写游戏。 我忽略了使用Guide.BeginShowKeyboardInput,因为它打破了自包含游戏的感觉,而且指南总是显示XBOX按钮这一事实对我来说也不合适。 是的,这是最简单的方法,但我不喜欢它。

接下来我尝试使用System.Windows.Forms.NativeWindow。 我创建了一个inheritance自它的类,并将它传递给了Games窗口句柄,实现了WndProc函数来捕获WM_CHAR(或WM_KEYDOWN),尽管WndProc被调用了其他消息,WM_CHAR和WM_KEYDOWN从未这样做过。 所以我不得不放弃这个想法,此外,我还引用了整个Windows表单,这意味着不必要的内存占用空间。

所以我的最后一个想法是创建一个Thread级别的低级键盘钩子。 到目前为止,这是最成功的。 我收到WM_KEYDOWN消息,(还没试过WM_CHAR)将Win32函数MapVirtualKey的虚拟键码转换为char。 我收到了我的文字! (我现在只是用Debug.Write打印)

但有几个问题。 好像我有大写锁定,并且没有响应的移位键。 (当然不是,它只是每个键只有一个虚拟键代码,所以只翻译它有一个输出)并且它增加了开销,因为它将自己附加到Windows Hook列表并且没有我那么快d喜欢它,但缓慢可能更多是由于Debug.Write。

有没有其他人接近这个并解决它,而不必求助于屏幕键盘? 或者有没有人有进一步的想法让我尝试?

提前致谢。

吉米问的问题

也许我不理解这个问题,但为什么你不能使用XNA Keyboard和KeyboardState类?

我的评论:

这是因为虽然您可以读取密钥状态,但您无法访问键入的文本以及用户输入的方式。

所以让我进一步澄清一下。 我想实现能够从用户读取文本输入,就像他们在文本框中键入是Windows一样。 键盘和KeyboardState类获取所有键的状态,但我必须将每个键和组合映射到它的字符表示。 当用户不使用与我特别用符号相同的键盘语言时,这就会失败(我的双引号是shift + 2,而美国键盘在返回键附近有它们的位置)。


似乎我的窗口挂钩是要走的路,只是我没有得到WM_CHAR的原因是因为XNA消息泵没有做翻译消息。

每当我收到WM _ KEYDOWN消息时添加TranslateMessage意味着我得到了WM_CHAR消息,然后我用它在我的MessageHuff类中发出了一个字符类型的事件,我的KeyboardBuffer类已订阅了该事件,然后将其缓存到文本缓冲区:D (或StringBuilder,但结果是一样的)

所以我按照自己的意愿工作。

非常感谢Jimmy提供了一个非常丰富的信息链接。

也许我不理解这个问题,但为什么你不能使用XNA Keyboard和KeyboardState类?

用于在XNA中添加Windows挂钩

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Diagnostics; using System.Reflection; /* Author: Sekhat * * License: Public Domain. * * Usage: * * Inherit from this class, and override the WndProc function in your derived class, * in which you handle your windows messages. * * To start recieving the message, create an instance of your derived class, passing in the * window handle of the window you want to listen for messages for. * * in XNA: this would be the Game.Window.Handle property * in Winforms Form.Handle property */ namespace WindowsHookExample { public abstract class WindowsHook : IDisposable { IntPtr hHook; IntPtr hWnd; // Stored here to stop it from getting garbage collected Win32.WndProcDelegate wndProcDelegate; public WindowsHook(IntPtr hWnd) { this.hWnd = hWnd; wndProcDelegate = WndProcHook; CreateHook(); } ~WindowsHook() { Dispose(false); } private void CreateHook() { uint threadId = Win32.GetWindowThreadProcessId(hWnd, IntPtr.Zero); hHook = Win32.SetWindowsHookEx(Win32.HookType.WH_CALLWNDPROC, wndProcDelegate, IntPtr.Zero, threadId); } private int WndProcHook(int nCode, IntPtr wParam, ref Win32.Message lParam) { if (nCode >= 0) { Win32.TranslateMessage(ref lParam); // You may want to remove this line, if you find your not quite getting the right messages through. This is here so that WM_CHAR is correctly called when a key is pressed. WndProc(ref lParam); } return Win32.CallNextHookEx(hHook, nCode, wParam, ref lParam); } protected abstract void WndProc(ref Win32.Message message); #region Interop Stuff // I say thankya to P/Invoke.net. // Contains all the Win32 functions I need to deal with protected static class Win32 { public enum HookType : int { WH_JOURNALRECORD = 0, WH_JOURNALPLAYBACK = 1, WH_KEYBOARD = 2, WH_GETMESSAGE = 3, WH_CALLWNDPROC = 4, WH_CBT = 5, WH_SYSMSGFILTER = 6, WH_MOUSE = 7, WH_HARDWARE = 8, WH_DEBUG = 9, WH_SHELL = 10, WH_FOREGROUNDIDLE = 11, WH_CALLWNDPROCRET = 12, WH_KEYBOARD_LL = 13, WH_MOUSE_LL = 14 } public struct Message { public IntPtr lparam; public IntPtr wparam; public uint msg; public IntPtr hWnd; } ///  /// Defines the windows proc delegate to pass into the windows hook ///  public delegate int WndProcDelegate(int nCode, IntPtr wParam, ref Message m); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SetWindowsHookEx(HookType hook, WndProcDelegate callback, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref Message m); [DllImport("coredll.dll", SetLastError = true)] public static extern IntPtr GetModuleHandle(string module); [DllImport("user32.dll", EntryPoint = "TranslateMessage")] public extern static bool TranslateMessage(ref Message m); [DllImport("user32.dll")] public extern static uint GetWindowThreadProcessId(IntPtr window, IntPtr module); } #endregion #region IDisposable Members public void Dispose() { Dispose(true); } private void Dispose(bool disposing) { if (disposing) { // Free managed resources here } // Free unmanaged resources here if (hHook != IntPtr.Zero) { Win32.UnhookWindowsHookEx(hHook); } } #endregion } } 

我使用了这个gamedev.netpost的解决方案,它很棒:)

这是一个简单的方法,IMO,有spacebackAZ ,然后是特殊字符!,@,#,$,%,^,&,*,(,) 。 (注意,您需要导入System.Linq )以下是字段:

 Keys[] keys; bool[] IskeyUp; string[] SC = { ")" , "!", "@", "#", "$", "%", "^", "&", "*", "("};//special characters 

构造函数:

 keys = new Keys[38]; Keys[] tempkeys; tempkeys = Enum.GetValues(typeof(Keys)).Cast().ToArray(); int j = 0; for (int i = 0; i < tempkeys.Length; i++) { if (i == 1 || i == 11 || (i > 26 && i < 63))//get the keys listed above as well as AZ { keys[j] = tempkeys[i];//fill our key array j++; } } IskeyUp = new bool[keys.Length]; //boolean for each key to make the user have to release the key before adding to the string for (int i = 0; i < keys.Length; i++) IskeyUp[i] = true; 

最后,更新方法:

 string result = ""; public override void Update(GameTime gameTime) { KeyboardState state = Keyboard.GetState(); int i = 0; foreach (Keys key in keys) { if (state.IsKeyDown(key)) { if (IskeyUp[i]) { if (key == Keys.Back && result != "") result = result.Remove(result.Length - 1); if (key == Keys.Space) result += " "; if (i > 1 && i < 12) { if (state.IsKeyDown(Keys.RightShift) || state.IsKeyDown(Keys.LeftShift)) result += SC[i - 2];//if shift is down, and a number is pressed, using the special key else result += key.ToString()[1]; } if (i > 11 && i < 38) { if (state.IsKeyDown(Keys.RightShift) || state.IsKeyDown(Keys.LeftShift)) result += key.ToString(); else result += key.ToString().ToLower(); //return the lowercase char is shift is up. } } IskeyUp[i] = false; //make sure we know the key is pressed } else if (state.IsKeyUp(key)) IskeyUp[i] = true; i++; } base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); } 

希望这很好帮助。 我个人认为它比钩子更容易使用,这也可以很容易地修改特殊结果(如洗牌)。

这个页面是关于xna中关于WM_CHAR拦截的谷歌搜索结果,所以我在这里留下一些注意事项。 也许这对其他人有用(如果他们能够理解我的英语=)))。

我尝试使用来自Sekhat的windowshook代码,但似乎应该将WH_GETMESSAGE传递给SetWindowsHookEx而不是Win32.HookType.WH_CALLWNDPROC(仅使用WH_GETMESSAGE代码,lparaw将指向Win32.Message)。

还有一些重复的消息(使用wparam 0)。 (看这里 – http://msdn.microsoft.com/en-us/library/ms644981%28v=VS.85%29.aspx关于WPARAM中的PM_NOREMOVE / PM_REMOVE)

当我添加这样的东西

  if (nCode >= 0 && wParam == 1) { Win32.TranslateMessage(ref lParam); WndProc(ref lParam); } 

wm_keypress wm_char重复停止(我认为1是PM_NOREMOVE或PM_REMOVE)。

PS nuclex变体现在显示404页面,但可以使用webarchive查看。 nuclex变体工作,但它导致从原生XNA MouseState(在XNA 3.1上)处理中断的mouseWheel处理=(