Jump to content

  • Log In with Google      Sign In   
  • Create Account


[.net] [XNA] Getting Text from keyboard


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
20 replies to this topic

#1 ArchG   Members   -  Reputation: 327

Like
0Likes
Like

Posted 29 July 2007 - 04:13 PM

Hello, Making a small online game in C#, using the XNA framework. I'd like to make it so you 'chat' in this game (using the keyboard)...but so far..I can't find any good way to get text that the user types in...it seems my only options are
KeyboardState keyboard = Keyboard.GetState();
keyboard.IsKeyPressed(Key);
There is no way that would ever work..unless i'm suppose to poll every key. the other way was the
keyboard.GetPressedKeys();
which doesn't give me help either. There has to be some simple way to get text that the user types in. For example if they wanted to chat..."Hi My name is Kyle"....how would I grab the text that they entered? I hope I made this clear enough.. Thanks, ArchG

Sponsor:

#2 Nick Gravelyn   Members   -  Reputation: 846

Like
0Likes
Like

Posted 29 July 2007 - 04:17 PM

Something like that is far beyond what XNA is. At it's core XNA is just a fancy wrapper around DirectX and XInput. So yes, you will have to manually check each key and add it to the end of a string. It sucks, but just make it a class that you can reuse and then it won't seem so bad.

#3 ArchG   Members   -  Reputation: 327

Like
0Likes
Like

Posted 29 July 2007 - 04:54 PM

thanks for the quick reply.

I found a solution that benryves made, it's in his journal if anyone is interested.

#4 Krisc   Members   -  Reputation: 494

Like
0Likes
Like

Posted 03 August 2007 - 07:58 AM

What you are looking for is Event Based Input rather than Immediate Data. My project, Thrust, has a few classes in the Thrust.Input library to handle these types of events for the keyboard, mouse and gamepad.

#5 keshtath   Members   -  Reputation: 146

Like
2Likes
Like

Posted 20 August 2007 - 10:50 AM

Here's some simple sample code I put together to address this issue, it needs to be refactored but should show a decent way to handle keyboard input.

private void UpdateInput()
{
oldKeyboardState = currentKeyboardState;
currentKeyboardState = Keyboard.GetState();

Keys[] pressedKeys;
pressedKeys = currentKeyboardState.GetPressedKeys();

foreach (Keys key in pressedKeys)
{
if( oldKeyboardState.IsKeyUp(key) )
{
if (key == Keys.Back) // overflows
textString = textString.Remove(textString.Length - 1, 1);
else
if (key == Keys.Space)
textString = textString.Insert(textString.Length, " ");
else
textString += key.ToString();
}
}
}

#6 Thevenin   Members   -  Reputation: 270

Like
0Likes
Like

Posted 21 August 2007 - 12:22 PM

Quote:
Original post by NickGravelyn
Something like that is far beyond what XNA is. At it's core XNA is just a fancy wrapper around DirectX and XInput. So yes, you will have to manually check each key and add it to the end of a string. It sucks, but just make it a class that you can reuse and then it won't seem so bad.


Eh? Even DirectX has support for event based input (kinda; directInput had a toggle to do BufferedInput, which completely solves the cons about poll-based input. Furthermore, DirectX does not have methods to create a Window, DirectX programmers had to create their window with Win32 and then build a DirectX context into it -- XNA does this for you.

Quote:
Original post by ArchG
For example if they wanted to chat..."Hi My name is Kyle"....how would I grab the text that they entered?


You can't.

Currently, XNA sucks for getting input, even for simple games (like Tetris); it's like a trademark suckyness similar to RPGMaker2000 games wherein you find yourself saying "this game must be made in RPGMaker2000 because of {some suckyness}. You can alleviate this by building a event-based system overtop of the poll-based system (shoving the frequncy reader on a seperate thread), but that won't solve all the issues. Lack of event-based input is one of the primary issues I've had with XNA. The best solution thus far is to embed XNA's MDX components into a WinForms application, and use WinForms as the framework for event-based input. This way ditch everything that differentiates XNA from MDX; no more XNA Input, no more XNA Window, and no more XNA Game class. You'll have to resort back to handling your own devicelost exceptions, and the like, just as you would with MDX.

benryves has a couple solutions. One is embedding XNA into a WinForm (which I do aswell), another is a GlobalKeyHook. Considering both of these solutions rely on hacks&/otherwise_abusive_methods I'd go with the WinForms if but for the sole reason that you won't find yourself locked out of other important features (Such as Input Method Editors).

[Edited by - Thevenin on August 21, 2007 6:22:28 PM]

#7 Nick Gravelyn   Members   -  Reputation: 846

Like
0Likes
Like

Posted 21 August 2007 - 02:55 PM

Quote:
Original post by Thevenin
Quote:
Original post by NickGravelyn
Something like that is far beyond what XNA is. At it's core XNA is just a fancy wrapper around DirectX and XInput. So yes, you will have to manually check each key and add it to the end of a string. It sucks, but just make it a class that you can reuse and then it won't seem so bad.


Eh? Even DirectX has support for event based input (kinda; directInput had a toggle to do BufferedInput, which completely solves the cons about poll-based input. Furthermore, DirectX does not have methods to create a Window, DirectX programmers had to create their window with Win32 and then build a DirectX context into it -- XNA does this for you.

Like I said, it's a fancy wrapper. It tosses in the game loop, window management, and a few other goodies.

Quote:
Currently, XNA sucks for getting input, even for simple games (like Tetris); it's like a trademark suckyness similar to RPGMaker2000 games wherein you find yourself saying "this game must be made in RPGMaker2000 because of {some suckyness}. You can alleviate this by building a event-based system overtop of the poll-based system (shoving the frequncy reader on a seperate thread), but that won't solve all the issues. Lack of event-based input is one of the primary issues I've had with XNA. The best solution thus far is to embed XNA's MDX components into a WinForms application, and use WinForms as the framework for event-based input. This way ditch everything that differentiates XNA from MDX; no more XNA Input, no more XNA Window, and no more XNA Game class. You'll have to resort back to handling your own devicelost exceptions, and the like, just as you would with MDX.

benryves has a couple solutions. One is embedding XNA into a WinForm (which I do aswell), another is a GlobalKeyHook. Considering both of these solutions rely on hacks&/otherwise_abusive_methods I'd go with the WinForms if but for the sole reason that you won't find yourself locked out of other important features (Such as Input Method Editors).

I think the decision to go with a polling system instead of event based is that it simply doesn't make much sense for a vast majority of games. Beyond that it then becomes harder to get those inputs in various places. Right now you can call Keyboard.GetState() anywhere you are using the Input namespace. If you had an event system, you'd have to tie in events for anything reading the input or start passing around the keyboard state. I believe XNA is better with the polling system.

Beyond that I was able to build a simple event based system with key repeats and code to then interpret the inputs into text in a matter of about a half hour. It's not too challenging.



#8 keshtath   Members   -  Reputation: 146

Like
0Likes
Like

Posted 21 August 2007 - 07:33 PM

I would love to see how other people have implemented reading keyboard input in XNA. I'll post some updated code one I add some more functionality and error checking.


#9 Nypyren   Crossbones+   -  Reputation: 3939

Like
0Likes
Like

Posted 21 August 2007 - 08:46 PM

I'm feeling generous. Here's a dump of code from my project (caution: may be missing required definitions - I almost forgot the Win32 DllImport stuff).

It should be noted that the key-to-char conversion is a pain and the snippets below is only known to work for QWERTY English. I'm not sure if Dvorak layouts work or not.

The code below is broken into two parts: A KeyboardTracker class that tracks keydown / keyup changes, and a few functions from a GuiManager class that convert that into events similar to how Systems.Windows.Forms control keyboard handling works.


namespace Win32
{
public enum VKey
{
// Tons of other VK_ codes removed.
CAPITAL = 0x14,
NUMLOCK = 0x90,
SCROLL = 0x91,
}

public static class User
{
[DllImport("user32.dll")]
public static extern short GetKeyState(VKey vkey);
}
}

public enum KeyState
{
Up, Down, NewUp, NewDown
}

public class KeyboardTracker
{
static TimeSpan keyDelayTime;
static TimeSpan keyRepeatTime;

KeyState[] keyStates;
Keys lastPressedKey;
TimeSpan nextRepeatTime;
int repeatSignal;

bool capsLock;
bool numLock;
bool scrollLock;

public bool AltDown
{
get { return keyStates[(int)Keys.LeftAlt] == KeyState.Down || keyStates[(int)Keys.RightAlt] == KeyState.Down; }
}

public bool CtrlDown
{
get { return keyStates[(int)Keys.LeftControl] == KeyState.Down || keyStates[(int)Keys.RightControl] == KeyState.Down; }
}

public bool ShiftDown
{
get { return keyStates[(int)Keys.LeftShift] == KeyState.Down || keyStates[(int)Keys.RightShift] == KeyState.Down; }
}

public bool CapsLock { get { return capsLock; } }
public bool NumLock { get { return numLock; } }
public bool ScrollLock { get { return scrollLock; } }

public KeyState[] KeyStates
{
get { return keyStates; }
}

public Keys LastPressedKey
{
get { return lastPressedKey; }
}

public int RepeatSignal
{
get { return repeatSignal; }
}

static KeyboardTracker()
{
keyDelayTime = new TimeSpan(0, 0, 0, 0, 250 + 250*SystemInformation.KeyboardDelay);
keyRepeatTime = new TimeSpan(0, 0, 0, 0, 33 + (400-33) * (31 - SystemInformation.KeyboardSpeed) / 31);
}

public KeyboardTracker()
{
Keys[] values = (Keys[])Enum.GetValues(typeof(Keys));

int maxIndex = 0;
foreach (Keys key in values)
{
if ((int)key > maxIndex)
maxIndex = (int)key;
}

lastPressedKey = Keys.None;

keyStates = new KeyState[maxIndex+1];
}

public void Update(GameTime gameTime)
{
KeyState[] tempStates = new KeyState[keyStates.Length];

KeyboardState state = Keyboard.GetState();

capsLock = (Win32.User.GetKeyState(Win32.VKey.CAPITAL) & 1) != 0;
numLock = (Win32.User.GetKeyState(Win32.VKey.NUMLOCK) & 1) != 0;
scrollLock = (Win32.User.GetKeyState(Win32.VKey.SCROLL) & 1) != 0;

Keys[] keys = state.GetPressedKeys();

for (int i=0; i<keys.Length; ++i)
{
tempStates[(int)keys[i]] = KeyState.Down;
}

repeatSignal = 0;

for (int i=0; i<keyStates.Length; ++i)
{
switch (keyStates[i])
{
case KeyState.Up:
if (tempStates[i] == KeyState.Down) keyStates[i] = KeyState.NewDown;
break;

case KeyState.Down:
if (tempStates[i] == KeyState.Up) keyStates[i] = KeyState.NewUp;
break;

case KeyState.NewDown:
if (tempStates[i] == KeyState.Down) keyStates[i] = KeyState.Down;
else keyStates[i] = KeyState.NewUp;
break;

case KeyState.NewUp:
if (tempStates[i] == KeyState.Up) keyStates[i] = KeyState.Up;
else keyStates[i] = KeyState.NewDown;
break;
}

if (keyStates[i] == KeyState.NewDown)
{
Keys newPressedKey = (Keys)i;

if (newPressedKey != lastPressedKey)
{
nextRepeatTime = gameTime.TotalRealTime + keyDelayTime;
repeatSignal = 1;
}
lastPressedKey = newPressedKey;
}
}

if (keyStates[(int)lastPressedKey] == KeyState.Up || keyStates[(int)lastPressedKey] == KeyState.NewUp)
{
lastPressedKey = Keys.None;
}
else
{
while (gameTime.TotalRealTime > nextRepeatTime)
{
repeatSignal++;
nextRepeatTime += keyRepeatTime;
}
}
}

public static char TranslateChar(Keys key, bool shift, bool capsLock, bool numLock)
{
switch (key)
{
case Keys.A: return TranslateAlphabetic('a', shift, capsLock);
case Keys.B: return TranslateAlphabetic('b', shift, capsLock);
case Keys.C: return TranslateAlphabetic('c', shift, capsLock);
case Keys.D: return TranslateAlphabetic('d', shift, capsLock);
case Keys.E: return TranslateAlphabetic('e', shift, capsLock);
case Keys.F: return TranslateAlphabetic('f', shift, capsLock);
case Keys.G: return TranslateAlphabetic('g', shift, capsLock);
case Keys.H: return TranslateAlphabetic('h', shift, capsLock);
case Keys.I: return TranslateAlphabetic('i', shift, capsLock);
case Keys.J: return TranslateAlphabetic('j', shift, capsLock);
case Keys.K: return TranslateAlphabetic('k', shift, capsLock);
case Keys.L: return TranslateAlphabetic('l', shift, capsLock);
case Keys.M: return TranslateAlphabetic('m', shift, capsLock);
case Keys.N: return TranslateAlphabetic('n', shift, capsLock);
case Keys.O: return TranslateAlphabetic('o', shift, capsLock);
case Keys.P: return TranslateAlphabetic('p', shift, capsLock);
case Keys.Q: return TranslateAlphabetic('q', shift, capsLock);
case Keys.R: return TranslateAlphabetic('r', shift, capsLock);
case Keys.S: return TranslateAlphabetic('s', shift, capsLock);
case Keys.T: return TranslateAlphabetic('t', shift, capsLock);
case Keys.U: return TranslateAlphabetic('u', shift, capsLock);
case Keys.V: return TranslateAlphabetic('v', shift, capsLock);
case Keys.W: return TranslateAlphabetic('w', shift, capsLock);
case Keys.X: return TranslateAlphabetic('x', shift, capsLock);
case Keys.Y: return TranslateAlphabetic('y', shift, capsLock);
case Keys.Z: return TranslateAlphabetic('z', shift, capsLock);

case Keys.D0: return (shift) ? ')' : '0';
case Keys.D1: return (shift) ? '!' : '1';
case Keys.D2: return (shift) ? '@' : '2';
case Keys.D3: return (shift) ? '#' : '3';
case Keys.D4: return (shift) ? '$' : '4';
case Keys.D5: return (shift) ? '%' : '5';
case Keys.D6: return (shift) ? '^' : '6';
case Keys.D7: return (shift) ? '&' : '7';
case Keys.D8: return (shift) ? '*' : '8';
case Keys.D9: return (shift) ? '(' : '9';

case Keys.Add: return '+';
case Keys.Divide: return '/';
case Keys.Multiply: return '*';
case Keys.Subtract: return '-';

case Keys.Space: return ' ';
case Keys.Tab: return '\t';

case Keys.Decimal: if (numLock && !shift) return '.'; break;
case Keys.NumPad0: if (numLock && !shift) return '0'; break;
case Keys.NumPad1: if (numLock && !shift) return '1'; break;
case Keys.NumPad2: if (numLock && !shift) return '2'; break;
case Keys.NumPad3: if (numLock && !shift) return '3'; break;
case Keys.NumPad4: if (numLock && !shift) return '4'; break;
case Keys.NumPad5: if (numLock && !shift) return '5'; break;
case Keys.NumPad6: if (numLock && !shift) return '6'; break;
case Keys.NumPad7: if (numLock && !shift) return '7'; break;
case Keys.NumPad8: if (numLock && !shift) return '8'; break;
case Keys.NumPad9: if (numLock && !shift) return '9'; break;

case Keys.OemBackslash: return shift ? '|' : '\\';
case Keys.OemCloseBrackets: return shift ? '}' : ']';
case Keys.OemComma: return shift ? '<' : ',';
case Keys.OemMinus: return shift ? '_' : '-';
case Keys.OemOpenBrackets: return shift ? '{' : '[';
case Keys.OemPeriod: return shift ? '>' : '.';
case Keys.OemPipe: return shift ? '|' : '\\';
case Keys.OemPlus: return shift ? '+' : '=';
case Keys.OemQuestion: return shift ? '?' : '/';
case Keys.OemQuotes: return shift ? '"' : '\'';
case Keys.OemSemicolon: return shift ? ':' : ';';
case Keys.OemTilde: return shift ? '~' : '`';
}

return (char)0;
}

public static char TranslateAlphabetic(char baseChar, bool shift, bool capsLock)
{
return (capsLock ^ shift) ? char.ToUpper(baseChar) : baseChar;
}
}









And the relevant sections of a System.Windows.Forms GUI manager emulator...


public class KeyEventArgs
{
public bool capsLock;
public bool numLock;
public bool scrollLock;

public bool alt;
public bool ctrl;
public bool shift;

public char character;
public readonly Keys key;

public KeyEventArgs(Keys key, bool alt, bool ctrl, bool shift, bool capsLock, bool numLock, bool scrollLock)
{
this.key = key;
this.alt = alt;
this.ctrl = ctrl;
this.shift = shift;
this.capsLock = capsLock;
this.numLock = numLock;
this.scrollLock = scrollLock;

character = KeyboardTracker.TranslateChar(key, shift, capsLock, numLock);
}
}

public class KeyPressEventArgs
{
public bool handled;
public char keychar;
public Keys key;

public KeyPressEventArgs(Keys key, char keychar)
{
this.key = key;
this.keychar = keychar;
}
}

public class PreviewKeyDownEventArgs
{
KeyEventArgs kea;
bool isInputKey;

public KeyEventArgs KeyEventArgs
{
get { return kea; }
}

public bool IsInputKey
{
get { return isInputKey; }
set { isInputKey = value; }
}

public PreviewKeyDownEventArgs(KeyEventArgs kea)
{
this.kea = kea;
}
}

public delegate void KeyEventHandler(object sender, KeyEventArgs kea);
public delegate void KeyPressEventHandler(object sender, KeyPressEventArgs kea);
public delegate void PreviewKeyDownEventHandler(object sender, PreviewKeyDownEventArgs pkea);

// The following code is snippets from class 'GuiManager'
// (which has so many other methods that I just ripped the keyboard functions out by themselves).

// The GuiManager is a class derived from a System.Windows.Forms.Control look-alike class
// from which I derive everything in my GUI system. The GuiManager originally was
// a stand-alone class, but composite child control handling led me to just derive
// from the Control class instead.

// An important part to note is that UpdateKeyboard should only be called
// if the application window has focus / is active.

KeyboardTracker keyboardTracker;


void UpdateKeyboard(GameTime gameTime)
{
keyboardTracker.Update(gameTime);

if (focusControl != null)
{
for (int i=0; i<keyboardTracker.KeyStates.Length; ++i)
{
switch (keyboardTracker.KeyStates[i])
{
case KeyState.NewUp:
OnKeyUp(new KeyEventArgs((Keys)i, keyboardTracker.AltDown, keyboardTracker.CtrlDown, keyboardTracker.ShiftDown,
keyboardTracker.CapsLock, keyboardTracker.NumLock, keyboardTracker.ScrollLock));
break;

case KeyState.NewDown:
if (keyboardTracker.LastPressedKey != (Keys)i) // Prevent two KeyDowns for the newest key.
{
OnKeyDown(new KeyEventArgs((Keys)i, keyboardTracker.AltDown, keyboardTracker.CtrlDown, keyboardTracker.ShiftDown,
keyboardTracker.CapsLock, keyboardTracker.NumLock, keyboardTracker.ScrollLock));
}
break;
}
}

// Key Repeats
if (keyboardTracker.LastPressedKey != Keys.None)
{
char character = KeyboardTracker.TranslateChar(
keyboardTracker.LastPressedKey, keyboardTracker.ShiftDown, keyboardTracker.CapsLock, keyboardTracker.NumLock);

for (int i=0; i<keyboardTracker.RepeatSignal; ++i)
{
OnKeyDown(new KeyEventArgs(keyboardTracker.LastPressedKey, keyboardTracker.AltDown, keyboardTracker.CtrlDown, keyboardTracker.ShiftDown,
keyboardTracker.CapsLock, keyboardTracker.NumLock, keyboardTracker.ScrollLock));

if (character != '\0')
{
OnKeyPress(new KeyPressEventArgs(keyboardTracker.LastPressedKey, character));
}
}
}
}
}

public override void OnKeyUp(KeyEventArgs kea)
{
if (focusControl.IsInputKey(kea.key))
{
focusControl.OnKeyUp(kea);
}
}
public override void OnKeyDown(KeyEventArgs kea)
{
if (focusControl.IsInputKey(kea.key))
{
focusControl.OnKeyDown(kea);
}
else
{
if (kea.key == Keys.Tab)
{
Control newTabStop = GetNextTabStop(focusControl, !kea.shift);

if (newTabStop != null)
{
Focus = newTabStop;
}
}
}
}
public override void OnKeyPress(KeyPressEventArgs kpea)
{
if (focusControl.IsInputKey(kpea.key))
{
focusControl.OnKeyPress(kpea);
}
}








(edit) Oops... forgot that the key preview stuff wasn't done yet. I removed it from the snippets.

(edit2) Trying to make the stupid 'source' blocks stay limited horizontally.

#10 Promit   Moderators   -  Reputation: 6332

Like
0Likes
Like

Posted 21 August 2007 - 09:44 PM

I talked to Thevenin about this whole thing tonight, and decided to fix it in the simplest, most direct way I could think of. Took a little while to install the various XNA bits, but that plus a half hour of poking around with some code finally solved things. Here's a mostly cleaned up version of the code, although it's still very much a first draft.

using System;
using System.Runtime.InteropServices;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace EventInput
{
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 delegate void CharEnteredHandler( object sender, CharacterEventArgs e );
public delegate void KeyEventHandler( object sender, KeyEventArgs e );

public static class EventInput
{
/// <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;

delegate IntPtr WndProc( IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam );

static bool initialized;
static IntPtr prevWndProc;
static WndProc hookProcDelegate;
static IntPtr hIMC;

//various Win32 constants that we need
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 = 0x0281;
const int WM_INPUTLANGCHANGE = 0x51;
const int WM_GETDLGCODE = 0x87;
const int WM_IME_COMPOSITION = 0x10f;
const int DLGC_WANTALLKEYS = 4;

//Win32 functions that we're using
[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 );

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

return returnCode;
}
}
}






So, general things of note:
* It picks up key up/down messages, and it's event based. It's also buffered, so you won't lose presses regardless of poll frequency.
* The CharEntered event does full textual translation. It understands keyboard layouts, it's aware of modifiers like shift, etc. If you type a capital letter, you get a capital letter.
* The whole thing is IME enabled. In other words, East Asian languages will function properly.

Usage is about as simple as it gets. Call EventInput.Initialize from your Game class initialize, and pass this.Window. Hook the events from there and you're done. There's nothing to create or keep track of, since it's a single static class.

[Edited by - Promit on August 22, 2007 4:44:06 PM]

#11 Nypyren   Crossbones+   -  Reputation: 3939

Like
0Likes
Like

Posted 24 August 2007 - 09:14 AM

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.

#12 Promit   Moderators   -  Reputation: 6332

Like
0Likes
Like

Posted 24 August 2007 - 09:50 AM

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]

#13 Nypyren   Crossbones+   -  Reputation: 3939

Like
0Likes
Like

Posted 24 August 2007 - 10:13 AM

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.

#14 Promit   Moderators   -  Reputation: 6332

Like
0Likes
Like

Posted 24 August 2007 - 10:19 AM

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]

#15 Nypyren   Crossbones+   -  Reputation: 3939

Like
0Likes
Like

Posted 24 August 2007 - 05:22 PM

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




#16 mydevnull   Members   -  Reputation: 122

Like
0Likes
Like

Posted 05 September 2007 - 05:04 AM

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

#17 mydevnull   Members   -  Reputation: 122

Like
0Likes
Like

Posted 05 September 2007 - 05:18 AM

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]

#18 Nypyren   Crossbones+   -  Reputation: 3939

Like
0Likes
Like

Posted 05 September 2007 - 06:19 AM

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.

#19 Promit   Moderators   -  Reputation: 6332

Like
0Likes
Like

Posted 05 September 2007 - 06:40 AM

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.

#20 leidegren   Members   -  Reputation: 139

Like
0Likes
Like

Posted 22 March 2008 - 07:41 AM

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




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS