【问题标题】:XNA - Keyboard text inputXNA - 键盘文本输入
【发布时间】:2008-12-17 17:24:53
【问题描述】:

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

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

所以我的最后一个想法是创建一个线程级的低级键盘挂钩。这是迄今为止最成功的一次。我收到 WM_KEYDOWN 消息,(尚未尝试 WM_CHAR)将带有 Win32 函数 MapVirtualKey 的虚拟键码转换为字符。我得到了我的文字! (我现在只是用 Debug.Write 打印)

虽然有几个问题。就好像我有大写锁定和无响应的换档键。 (当然不是,只是每个键只有一个虚拟键代码,因此翻译它只有一个输出)并且它会增加开销,因为它将自身附加到 Windows 挂钩列表并且不如我快'我喜欢它,但速度较慢可能更多是由于 Debug.Write。

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

提前致谢。

吉米提出的问题

也许我不明白这个问题,但你为什么不能使用 XNA 键盘和 KeyboardState 类?

我的评论:

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

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


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

每当我收到 WM_KEYDOWN 消息时添加 TranslateMessage 意味着我收到了 WM_CHAR 消息,然后我用它在我的 KeyboardBuffer 类订阅的 MessageHook 类中触发一个字符类型的事件,然后将其缓冲到一个文本缓冲区:D(或 StringBuilder,但结果相同)

所以我可以按照自己的意愿进行操作。

非常感谢 Jimmy 提供了一个非常有用的帖子的链接。

【问题讨论】:

    标签: c# .net xna


    【解决方案1】:

    用于在 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;
                }
    
                /// <summary>
                ///  Defines the windows proc delegate to pass into the windows hook
                /// </summary>                  
                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
        }
    }
    

    【讨论】:

    【解决方案2】:

    我使用了gamedev.net post 中的解决方案,效果很好:)

    【讨论】:

      【解决方案3】:

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

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

      构造函数:

      keys = new Keys[38];
      Keys[] tempkeys;
      tempkeys = Enum.GetValues(typeof(Keys)).Cast<Keys>().ToArray<Keys>();
      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 A-Z
          {
              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);
      }
      

      希望这对您有所帮助。我个人认为它比钩子更容易使用,而且也可以很容易地修改它以获得特殊结果(例如洗牌)。

      【讨论】:

      • 唯一的问题是它不是基于事件的。因此,如果用户键入的速度比您的更新速度快,就会错过按键操作。
      • @Sekhat,如果 Update 方法每 16 毫秒发生一次,则每秒更新 60 次。我非常怀疑用户每秒可以输入超过 60 个字符(即每分钟 3600 个字符)。 :) 几乎没有人可以输入 700 CPM(请参阅 typing-speed-test.aoeu.eu/?lang=en)我在第 80 个百分位并获得 330 CPM。
      • 如果我在 16 毫秒内按下并释放一个键,您的代码将永远不会注册它。这比听起来容易,相信我。
      • @Sekhat,您只是在猜测(您是否真的尝试过这样做?)。我试图按一个键不让它注册,但每个按键都注册了。另外,为什么用户要这么快地按下一个键?他们用GamePads 这样做吗? gamepad 是否也需要一个钩子才能使其工作?另外,请注意您接受了使用keyboardstate 的答案。
      • 一点也不猜测,当我最初用内置的 XNA 东西开发一些东西时,我遇到了这个问题。编辑:至于用户按那么快的键,对我来说,它只是在测试使用 KeyboardState 的初始输入代码时输入句子时发生的。
      【解决方案4】:

      也许我不明白这个问题,但你为什么不能使用 XNA 键盘和 KeyboardState 类?

      【讨论】:

      • 这是因为尽管您可以读取键状态,但您无法访问键入的文本以及用户键入的方式。
      • 啊,我明白了。过去,我为此编写了一个实用程序类,类似于gamedev.net/community/forums/topic.asp?topic_id=457783
      • 谢谢,看来我的窗口挂钩是要走的路,只是我没有得到 WM_CHAR 的原因是因为 XNA 消息泵不翻译消息。我会摆弄一下,看看我还能得到什么:)
      【解决方案5】:

      此页面位于关于 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)。

      附: nuclex 变体现在显示 404 页面,但可以使用 webarchive 查看。 nuclex 变体有效,但它会导致本地 XNA MouseState 的 mouseWheel 处理中断(在 XNA 3.1 上)=(

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-01
        相关资源
        最近更新 更多