代码之家  ›  专栏  ›  技术社区  ›  Sekhat

XNA-键盘文本输入

  •  15
  • Sekhat  · 技术社区  · 16 年前

    好的,所以基本上我想能够检索键盘文本。比如把文本输入文本字段之类的。我只是在为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转换为char。我收到了我的短信!(我只是用debug打印,现在就写)

    但也有一些问题。就好像我开了Caps Lock,还有一把没有反应的换档钥匙。(当然不是这样,只是每个键只有一个虚拟键代码,所以翻译它只有一个输出),当它连接到Windows挂钩列表时,会增加开销,而且速度不如我希望的快,但是由于debug.write,速度可能会慢一些。

    有没有其他人在不使用屏幕键盘的情况下就解决了这个问题?还是有人想让我试试?

    事先谢谢。

    吉米问的问题

    也许我不理解这个问题,但是为什么你不能使用XNA键盘和键盘状态类呢?

    我的评论:

    这是因为尽管您可以读取键状态,但您无法访问键入的文本以及用户如何键入它。

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


    似乎我的窗口钩子是一条路要走,只是我没有得到wm_char的原因是XNA消息泵没有翻译消息。

    每当我收到wm时添加translateMessage _ keydown消息意味着我得到了wm_char消息,然后我使用它在我的messagehook类中触发了一个字符类型的事件,我的keyboardbuffer类订阅了该事件,然后将其缓冲到文本缓冲区:d(或stringbuilder,但结果是相同的)

    所以我可以随心所欲地工作。

    非常感谢吉米提供了一个非常有用的链接。

    5 回复  |  直到 8 年前
        1
  •  1
  •   Jimmy    16 年前

    也许我不理解这个问题,但是为什么你不能使用XNA键盘和键盘状态类呢?

        2
  •  9
  •   Sekhat    8 年前

    用于在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
        }
    }
    
        3
  •  1
  •   nornagon    15 年前

    我用了这个溶液 gamedev.net post 而且效果很好:)

        4
  •  1
  •   davidsbro    11 年前

    这是一个简单的方法,我的意思是 space , back , A-Z 然后是特殊字符 !,@,#,$,%,^,&,*,(,) . (注意,您需要导入 System.Linq )以下是字段:

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

    Constructor:

    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);
    }
    

    希望能帮上忙。我个人认为它比钩子更容易使用,而且这也可以很容易地修改以获得特殊的结果(例如洗掉钥匙)。

        5
  •  0
  •   Zakus    15 年前

    这个页面是关于xna中wm_char拦截的google结果的顶部,所以我在这里留下一些注释。也许这对其他人有用(如果他们能理解我的英语)。

    我尝试使用Sekhat的windowshook代码,但似乎应该将wh_getmessage传递给setwindowshookex而不是win32.hooktype.wh_callwndproc(只有wh_getmessage code lparaw将指向win32.message)。

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

    当我添加这样的内容时

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

    wm_键按下wm_char复制停止(i supose 1为pm_noremove或pm_remove)。

    附笔。 nuclex变体现在显示404页,但可以通过WebArchive查看。 nuclex变种有效,但它会导致鼠标断裂,从本地xna mousestate(在xna 3.1上)处理。=(