[.net] [XNA] Getting Text from keyboard

Started by
19 comments, last by Headkaze 16 years, 1 month ago
Impressive! Mine has issues with polling speed. I was just getting by due to high polling rate and long keyboard up/down times. I discovered that my similar solution for polled mouse handling doesn't play well with a tablet PC touch screen or Wacom Pen since the down duration is less than one polling cycle. Then I discovered that macro utility programs that rapidly send key down/up events with nearly no delay are completely missed by my solution. Obviously I need something else.

I think I'll probably scrap my existing system and go with something along the lines of what you've got since it'll solve all of the problems my current system has.
Advertisement
I forgot to mention that it's broken for 64 bit. Technically you're supposed to call SetWindowLongPtr, not SetWindowLong. It turns out, however, that 32 bit systems don't export any such function. it's actually just #define'd in the windows headers. I'm not sure exactly how to handle this correctly, but it'll need to be fixed for 64 bit. (Casting IntPtr -> int is bad mojo!)

[Edited by - Promit on August 24, 2007 4:50:32 PM]
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
That's good to know. However, does XNA support 64-bit yet? It would be moot to make the input handler 64-bit safe if you would have to run as a 32-bit process anyway.
Good point, and I have no idea.
[EDIT] I just checked. XNA does not, in fact, do 64 bit.

[Edited by - Promit on August 24, 2007 5:19:09 PM]
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Here's Promit's event driven system updated with mouse handling. Note: This also fixed all my problems with the touch sensor and Wacom Pen on my tablet PC.

using System;using System.Runtime.InteropServices;namespace Microsoft.Xna.Framework.Input{	public class CharacterEventArgs : EventArgs	{		private readonly char character;		private readonly int lParam;		public CharacterEventArgs(char character, int lParam)		{			this.character = character;			this.lParam = lParam;		}		public char Character		{			get { return character; }		}		public int Param		{			get { return lParam; }		}		public int RepeatCount		{			get { return lParam & 0xffff; }		}		public bool ExtendedKey		{			get { return (lParam & (1 << 24)) > 0; }		}		public bool AltPressed		{			get { return (lParam & (1 << 29)) > 0; }		}		public bool PreviousState		{			get { return (lParam & (1 << 30)) > 0; }		}		public bool TransitionState		{			get { return (lParam & (1 << 31)) > 0; }		}	}	public class KeyEventArgs : EventArgs	{		private Keys keyCode;		public KeyEventArgs(Keys keyCode)		{			this.keyCode = keyCode;		}		public Keys KeyCode		{			get { return keyCode; }		}	}		public class MouseEventArgs : EventArgs	{		private MouseButton button;		private int clicks;		private int x;		private int y;		private int delta;				public MouseButton Button { get { return button;         } }		public int Clicks         { get { return clicks;         } }		public int X              { get { return x;              } }		public int Y              { get { return y;              } }		public Point Location     { get { return new Point(x,y); } }		public int Delta          { get { return delta;          } }				public MouseEventArgs(MouseButton button, int clicks, int x, int y, int delta)		{			this.button = button;			this.clicks = clicks;			this.x = x;			this.y = y;			this.delta = delta;		}	}	public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);	public delegate void KeyEventHandler(object sender, KeyEventArgs e);		public delegate void MouseEventHandler(object sender, MouseEventArgs e);		/// <summary>	/// Mouse Key Flags from WinUser.h for mouse related WM messages.	/// </summary>	[Flags]	public enum MouseKeys	{		LButton =  0x01,		RButton =  0x02,		Shift =    0x04,		Control =  0x08,		MButton =  0x10,		XButton1 = 0x20,		XButton2 = 0x40	}		public enum MouseButton	{		None, Left, Right, Middle, X1, X2	}		public static class InputSystem	{		#region Events		/// <summary>		/// Event raised when a character has been entered.		/// </summary>		public static event CharEnteredHandler CharEntered;		/// <summary>		/// Event raised when a key has been pressed down. May fire multiple times due to keyboard repeat.		/// </summary>		public static event KeyEventHandler KeyDown;		/// <summary>		/// Event raised when a key has been released.		/// </summary>		public static event KeyEventHandler KeyUp;				/// <summary>		/// Event raised when a mouse button is pressed.		/// </summary>		public static event MouseEventHandler MouseDown;				/// <summary>		/// Event raised when a mouse button is released.		/// </summary>		public static event MouseEventHandler MouseUp;				/// <summary>		/// Event raised when the mouse changes location.		/// </summary>		public static event MouseEventHandler MouseMove;				/// <summary>		/// Event raised when the mouse has hovered in the same location for a short period of time.		/// </summary>		public static event MouseEventHandler MouseHover;				/// <summary>		/// Event raised when the mouse wheel has been moved.		/// </summary>		public static event MouseEventHandler MouseWheel;				/// <summary>		/// Event raised when a mouse button has been double clicked.		/// </summary>		public static event MouseEventHandler MouseDoubleClick;		#endregion		delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);				static bool initialized;		static IntPtr prevWndProc;		static WndProc hookProcDelegate;		static IntPtr hIMC;		#region Win32 Constants		const int GWL_WNDPROC = -4;				const int WM_KEYDOWN         = 0x100;		const int WM_KEYUP           = 0x101;		const int WM_CHAR            = 0x102;		const int WM_IME_SETCONTEXT  = 0x281;		const int WM_INPUTLANGCHANGE = 0x51;		const int WM_GETDLGCODE      = 0x87;		const int WM_IME_COMPOSITION = 0x10F;				const int DLGC_WANTALLKEYS   = 4;				const int WM_MOUSEMOVE =     0x200;				const int WM_LBUTTONDOWN =   0x201;		const int WM_LBUTTONUP =     0x202;		const int WM_LBUTTONDBLCLK = 0x203;				const int WM_RBUTTONDOWN =   0x204;		const int WM_RBUTTONUP =     0x205;		const int WM_RBUTTONDBLCLK = 0x206;				const int WM_MBUTTONDOWN =   0x207;		const int WM_MBUTTONUP =     0x208;		const int WM_MBUTTONDBLCLK = 0x209;				const int WM_MOUSEWHEEL =    0x20A;				const int WM_XBUTTONDOWN =   0x20B;		const int WM_XBUTTONUP =     0x20C;		const int WM_XBUTTONDBLCLK = 0x20D;				const int WM_MOUSEHOVER =    0x2A1;		#endregion		#region DLL Imports		[DllImport("Imm32.dll")]		static extern IntPtr ImmGetContext(IntPtr hWnd);		[DllImport("Imm32.dll")]		static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);		[DllImport("user32.dll")]		static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);		[DllImport("user32.dll")]		static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);		#endregion				public static Point MouseLocation		{			get			{				MouseState state = Mouse.GetState();				return new Point(state.X, state.Y);			}		}				public static bool ShiftDown		{			get			{				KeyboardState state = Keyboard.GetState();				return state.IsKeyDown(Keys.LeftShift) || state.IsKeyDown(Keys.RightShift);			}		}		public static bool CtrlDown		{			get			{				KeyboardState state = Keyboard.GetState();				return state.IsKeyDown(Keys.LeftControl) || state.IsKeyDown(Keys.RightControl);			}		}		public static bool AltDown		{			get			{				KeyboardState state = Keyboard.GetState();				return state.IsKeyDown(Keys.LeftAlt) || state.IsKeyDown(Keys.RightAlt);			}		}						/// <summary>		/// Initialize the TextInput with the given GameWindow.		/// </summary>		/// <param name="window">The XNA window to which text input should be linked.</param>		public static void Initialize(GameWindow window)		{			if (initialized)				throw new InvalidOperationException("TextInput.Initialize can only be called once!");			hookProcDelegate = new WndProc(HookProc);			prevWndProc = (IntPtr)SetWindowLong(window.Handle, GWL_WNDPROC, (int)Marshal.GetFunctionPointerForDelegate(hookProcDelegate));			hIMC = ImmGetContext(window.Handle);			initialized = true;		}		static IntPtr HookProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)		{			IntPtr returnCode = CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam);			switch (msg)			{				case WM_GETDLGCODE:					returnCode = (IntPtr)(returnCode.ToInt32() | DLGC_WANTALLKEYS);					break;				case WM_KEYDOWN:					if (KeyDown != null)						KeyDown(null, new KeyEventArgs((Keys)wParam));					break;				case WM_KEYUP:					if (KeyUp != null)						KeyUp(null, new KeyEventArgs((Keys)wParam));					break;				case WM_CHAR:					if (CharEntered != null)						CharEntered(null, new CharacterEventArgs((char)wParam, lParam.ToInt32()));					break;				case WM_IME_SETCONTEXT:					if (wParam.ToInt32() == 1)						ImmAssociateContext(hWnd, hIMC);					break;				case WM_INPUTLANGCHANGE:					ImmAssociateContext(hWnd, hIMC);					returnCode = (IntPtr)1;					break;												// Mouse messages				case WM_MOUSEMOVE:					if (MouseMove != null)					{						short x, y;						MouseLocationFromLParam(lParam.ToInt32(), out x, out y);												MouseMove(null, new MouseEventArgs(MouseButton.None, 0, x, y, 0));					}					break;								case WM_MOUSEHOVER:					if (MouseHover != null)					{						short x, y;						MouseLocationFromLParam(lParam.ToInt32(), out x, out y);												MouseHover(null, new MouseEventArgs(MouseButton.None, 0, x, y, 0));					}					break;								case WM_MOUSEWHEEL:					if (MouseWheel != null)					{							short x, y;						MouseLocationFromLParam(lParam.ToInt32(), out x, out y);												MouseWheel(null, new MouseEventArgs(MouseButton.None, 0, x, y, (wParam.ToInt32() >> 16) / 120));					}					break;				case WM_LBUTTONDOWN:   RaiseMouseDownEvent    (MouseButton.Left, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_LBUTTONUP:     RaiseMouseUpEvent      (MouseButton.Left, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_LBUTTONDBLCLK: RaiseMouseDblClickEvent(MouseButton.Left, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_RBUTTONDOWN:   RaiseMouseDownEvent    (MouseButton.Right, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_RBUTTONUP:     RaiseMouseUpEvent      (MouseButton.Right, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_RBUTTONDBLCLK: RaiseMouseDblClickEvent(MouseButton.Right, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_MBUTTONDOWN:   RaiseMouseDownEvent    (MouseButton.Middle, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_MBUTTONUP:     RaiseMouseUpEvent      (MouseButton.Middle, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_MBUTTONDBLCLK: RaiseMouseDblClickEvent(MouseButton.Middle, wParam.ToInt32(), lParam.ToInt32()); break;				case WM_XBUTTONDOWN:					if ((wParam.ToInt32() & 0x10000) != 0)					{						RaiseMouseDownEvent(MouseButton.X1, wParam.ToInt32(), lParam.ToInt32());					}					else if ((wParam.ToInt32() & 0x20000) != 0)					{						RaiseMouseDownEvent(MouseButton.X2, wParam.ToInt32(), lParam.ToInt32());					}					break;								case WM_XBUTTONUP:					if ((wParam.ToInt32() & 0x10000) != 0)					{						RaiseMouseUpEvent(MouseButton.X1, wParam.ToInt32(), lParam.ToInt32());					}					else if ((wParam.ToInt32() & 0x20000) != 0)					{						RaiseMouseUpEvent(MouseButton.X2, wParam.ToInt32(), lParam.ToInt32());					}					break;								case WM_XBUTTONDBLCLK:					if ((wParam.ToInt32() & 0x10000) != 0)					{						RaiseMouseDblClickEvent(MouseButton.X1, wParam.ToInt32(), lParam.ToInt32());					}					else if ((wParam.ToInt32() & 0x20000) != 0)					{						RaiseMouseDblClickEvent(MouseButton.X2, wParam.ToInt32(), lParam.ToInt32());					}					break;			}			return returnCode;		}				#region Mouse Message Helpers		static void RaiseMouseDownEvent(MouseButton button, int wParam, int lParam)		{			if (MouseDown != null)			{				short x, y;				MouseLocationFromLParam(lParam, out x, out y);								MouseDown(null, new MouseEventArgs(button, 1, x, y, 0));			}		}		static void RaiseMouseUpEvent(MouseButton button, int wParam, int lParam)		{			if (MouseUp != null)			{				short x, y;				MouseLocationFromLParam(lParam, out x, out y);				MouseUp(null, new MouseEventArgs(button, 1, x, y, 0));			}		}		static void RaiseMouseDblClickEvent(MouseButton button, int wParam, int lParam)		{			if (MouseDoubleClick != null)			{				short x, y;				MouseLocationFromLParam(lParam, out x, out y);				MouseDoubleClick(null, new MouseEventArgs(button, 1, x, y, 0));			}		}				static void MouseLocationFromLParam(int lParam, out short x, out short y)		{			// Cast to signed shorts to get sign extension on negative coordinates (of course this would only be possible if mouse capture was enabled).			x = (short)(lParam & 0xFFFF);			y = (short)(lParam >> 16);		}		#endregion	}}
Hello,

the InputSystem is great, I love it!
But I've one question.

Currently I use a "CharEntered"-handler for handling normal text and a "keydown"-handler for cursor control (move cursor left, right, to home, to end, ...).
So far so good.
Now I wanted to implement a marker function to mark text, and for that I have to know if SHIFT was pressed or not during the keydown-event of left arrow, right arrow, home or end.

Is this possible somehow?

Regards,
Daniel
Okay, did it the following way:

Enhanced the keydown-handler:

public delegate void KeyEventHandler(object sender, KeyEventArgs e, KeyboardState ks);

and added this to the hook wndproc:

case WM_KEYDOWN:
if (KeyDown != null)
KeyDown(null, new KeyEventArgs((Keys)wParam),Keyboard.GetState());
break;



*EDIT*

One question about the keycodes... When I press LEFTSHIFT the value e.keycode is 16! But (int)Keys.LeftShift is 160. How do you handle this?

Regards,
Daniel

[Edited by - mydevnull on September 5, 2007 12:18:20 PM]
I guess XNA defines its 'Keys' enumeration based on the GetKeyState / GetAsyncKeyState set of keys.

PlatformSDK WinUser.h:
#define VK_SHIFT          0x10.../* * VK_L* & VK_R* - left and right Alt, Ctrl and Shift virtual keys. * Used only as parameters to GetAsyncKeyState() and GetKeyState(). * No other API or message will distinguish left and right keys in this way. */#define VK_LSHIFT         0xA0#define VK_RSHIFT         0xA1#define VK_LCONTROL       0xA2#define VK_RCONTROL       0xA3#define VK_LMENU          0xA4#define VK_RMENU          0xA5


"No other API or message..." That means that WM_KEYDOWN won't use the left/right distinction.

HOWEVER... I believe WM_KEYDOWN's lParam includes the "extended key" bit which should distinguish between left and right alt, ctrl, and enter keys (and I think probably the shift key as well).

WM_KEYDOWN MSDN

So the solution may be to alter the KeyEventArgs constructor to take both the wParam and lParam, and do some VK-to-Keys changes using the lParam bits.
Quote:Original post by mydevnull
Okay, did it the following way:

I don't think this is actually legitimate, because you're relying on the key to still be pressed when the event gets processed. There should be info that you can get from the WM_KEYDOWN message that gives you the details about special keys and such, or you might just be able to keep track yourself. I'll have to skim MSDN's message stuff and see exactly how to handle it all correctly.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
A lot of ruckus for something quite simple. I noticed the lack of buffered input and made my own little hack for it. It works perfectly well, and doesn't really involve much code at all.

http://forums.xna.com/53074/ShowThread.aspx

This topic is closed to new replies.

Advertisement