C# XNA - 键盘文本输入
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/375316/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
XNA - Keyboard text input
提问by Sekhat
Okay, so basically I want to be able to retrieve keyboard text. Like entering text into a text field or something. I'm only writing my game for windows. I've disregarded using Guide.BeginShowKeyboardInput because it breaks the feel of a self contained game, and the fact that the Guide always shows XBOX buttons doesn't seem right to me either. Yes it's the easiest way, but I don't like it.
好的,所以基本上我希望能够检索键盘文本。就像在文本字段中输入文本一样。我只为 Windows 编写我的游戏。我忽略了使用 Guide.BeginShowKeyboardInput,因为它破坏了自包含游戏的感觉,而且 Guide 总是显示 XBOX 按钮这一事实对我来说似乎也不对。是的,这是最简单的方法,但我不喜欢它。
Next I tried using System.Windows.Forms.NativeWindow. I created a class that inherited from it, and passed it the Games window handle, implemented the WndProc function to catch WM_CHAR (or WM_KEYDOWN) though the WndProc got called for other messages, WM_CHAR and WM_KEYDOWN never did. So I had to abandon that idea, and besides, I was also referencing the whole of Windows forms, which meant unnecessary memory footprint bloat.
接下来我尝试使用 System.Windows.Forms.NativeWindow。我创建了一个继承自它的类,并将游戏窗口句柄传递给它,实现了 WndProc 函数来捕获 WM_CHAR(或 WM_KEYDOWN),尽管 WndProc 被其他消息调用,WM_CHAR 和 WM_KEYDOWN 从未这样做过。所以我不得不放弃这个想法,此外,我还引用了整个 Windows 窗体,这意味着不必要的内存占用膨胀。
So my last idea was to create a Thread level, low level keyboard hook. This has been the most successful so far. I get WM_KEYDOWN message, (not tried WM_CHAR yet) translate the virtual keycode with Win32 funcation MapVirtualKey to a char. And I get my text! (I'm just printing with Debug.Write at the moment)
所以我的最后一个想法是创建一个线程级别的低级别键盘钩子。这是迄今为止最成功的。我收到 WM_KEYDOWN 消息,(尚未尝试 WM_CHAR)将具有 Win32 功能 MapVirtualKey 的虚拟键码转换为字符。我得到了我的文字!(我现在只是用 Debug.Write 打印)
A couple problems though. It's as if I have caps lock on, and an unresponsive shift key. (Of course it's not however, it's just that there is only one Virtual Key Code per key, so translating it only has one output) and it adds overhead as it attaches itself to the Windows Hook List and isn't as fast as I'd like it to be, but the slowness could be more due to Debug.Write.
不过有几个问题。就好像我有大写锁定,还有一个没有反应的 shift 键。(当然不是,只是每个键只有一个虚拟键代码,因此翻译它只有一个输出)并且它增加了开销,因为它附加到 Windows 钩子列表并且没有我那么快'我喜欢它,但由于 Debug.Write,速度可能更慢。
Has anyone else approached this and solved it, without having to resort to an on screen keyboard? or does anyone have further ideas for me to try?
有没有其他人解决过这个问题并解决了它,而不必求助于屏幕键盘?或者有没有人有进一步的想法让我尝试?
thanks in advance.
提前致谢。
Question asked by Jimmy
Jimmy 提出的问题
Maybe I'm not understanding the question, but why can't you use the XNA Keyboard and KeyboardState classes?
也许我不明白这个问题,但你为什么不能使用 XNA 键盘和键盘状态类?
My comment:
我的评论:
It's because though you can read keystates, you can't get access to typed text as and how it is typed by the user.
这是因为虽然您可以读取键状态,但您无法访问键入的文本以及用户键入的方式。
So let me further clarify. I want to implement being able to read text input from the user as if they are typing into textbox is windows. The keyboard and KeyboardState class get states of all keys, but I'd have to map each key and combination to it's character representation. This falls over when the user doesn't use the same keyboard language as I do especially with symbols (my double quotes is shift + 2, while american keyboards have theirs somewhere near the return key).
所以让我进一步澄清。我想实现能够读取用户输入的文本,就好像他们在文本框中输入是 windows 一样。键盘和键盘状态类获取所有键的状态,但我必须将每个键和组合映射到它的字符表示。当用户不使用与我相同的键盘语言时,这会失败,尤其是使用符号时(我的双引号是 shift + 2,而美国键盘在返回键附近的某个地方有他们的双引号)。
it seems my window hook was the way to go, just the reason I wasn't getting WM_CHAR is because the XNA message pump doesn't do translate message.
看来我的窗口钩子是要走的路,只是我没有得到 WM_CHAR 的原因是因为 XNA 消息泵不翻译消息。
Adding TranslateMessage in whenever I received a WM_
KEYDOWN message meant I got my WM_CHAR message, I then used this to fire a character typed event in my MessageHook class which my KeyboardBuffer class had subscribed to, which then buffers that to a text buffer :D (or StringBuilder, but the result is the same)
每当我收到 WM _
KEYDOWN 消息时添加 TranslateMessage意味着我收到了 WM_CHAR 消息,然后我使用它在我的 KeyboardBuffer 类订阅的 MessageHook 类中触发字符类型事件,然后将其缓冲到文本缓冲区:D(或 StringBuilder,但结果是一样的)
So I have it all working as I want.
所以我让这一切都按我想要的方式工作。
Many thanks to Jimmy for a link to a very informative thread.
非常感谢 Jimmy 提供了一个非常有用的线程的链接。
采纳答案by Jimmy
Maybe I'm not understanding the question, but why can't you use the XNA Keyboard and KeyboardState classes?
也许我不明白这个问题,但你为什么不能使用 XNA 键盘和键盘状态类?
回答by Sekhat
For adding a windows hook in XNA
用于在 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
}
}
回答by Zakus
This page is on top of google result about WM_CHAR interception in xna, so i leave here some note. Maybe this will be useful for other (if they will be able to understand my English =)) ).
此页面位于有关 xna 中 WM_CHAR 拦截的 google 结果之上,因此我在此处留下一些注释。也许这对其他人有用(如果他们能够理解我的英语 =)))。
I try code with windowshook from Sekhat, but seems there should be WH_GETMESSAGE passed to SetWindowsHookEx instead Win32.HookType.WH_CALLWNDPROC (only with WH_GETMESSAGE code lparaw will point to Win32.Message ).
我尝试使用来自 Sekhat 的 windowsshook 代码,但似乎应该将 WH_GETMESSAGE 传递给 SetWindowsHookEx 而不是 Win32.HookType.WH_CALLWNDPROC(仅使用 WH_GETMESSAGE 代码 lparaw 将指向 Win32.Message )。
Also there is sometime duplicated messages (with wparam 0). (look here - http://msdn.microsoft.com/en-us/library/ms644981%28v=VS.85%29.aspxsomething about PM_NOREMOVE/PM_REMOVE in WPARAM )
有时还会有重复的消息(wparam 0)。(看这里 - http://msdn.microsoft.com/en-us/library/ms644981%28v=VS.85%29.aspx关于 WPARAM 中的 PM_NOREMOVE/PM_REMOVE 的一些东西)
When i add something like this
当我添加这样的东西时
if (nCode >= 0 && wParam == 1)
{
Win32.TranslateMessage(ref lParam);
WndProc(ref lParam);
}
wm_keypress wm_char duplication stopped (i supose 1 is PM_NOREMOVE or PM_REMOVE).
wm_keypress wm_char 重复停止(我假设 1 是 PM_NOREMOVE 或 PM_REMOVE)。
P.S. nuclex variant now show 404 page, but can be viewed with webarchive. nuclex variant works, but it cause broken mouseWheel processing from native XNA MouseState (on XNA 3.1) =(
PS nuclex 变体现在显示 404 页面,但可以使用 webarchive 查看。nuclex 变体有效,但它会导致本地 XNA MouseState(在 XNA 3.1 上)的 mouseWheel 处理中断 =(
回答by nornagon
I used the solution from this gamedev.net postand it works great :)
我使用了这个gamedev.net 帖子中的解决方案,效果很好:)
回答by davidsbro
Here's a simple way, IMO, to have the space
, back
, A-Z
and then the special chars !,@,#,$,%,^,&,*,(,)
. (Note, you need to import System.Linq
) Here are the fields:
这是一种简单的方法,IMO,可以使用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;
And Finally, the update method:
最后,更新方法:
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);
}
Hopefull this well help. I personally thought it was easier to use than the hooks, and this also can easily be modified for special results (such as shuffling the keys).
希望这很好帮助。我个人认为它比 hooks 更容易使用,并且这也可以很容易地修改以获得特殊结果(例如洗牌)。