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] = KeyState.Down; } repeatSignal = 0; for (int i=0; i<keyStates.Length; ++i) { switch (keyStates) { case KeyState.Up: if (tempStates == KeyState.Down) keyStates = KeyState.NewDown; break; case KeyState.Down: if (tempStates == KeyState.Up) keyStates = KeyState.NewUp; break; case KeyState.NewDown: if (tempStates == KeyState.Down) keyStates = KeyState.Down; else keyStates = KeyState.NewUp; break; case KeyState.NewUp: if (tempStates == KeyState.Up) keyStates = KeyState.Up; else keyStates = KeyState.NewDown; break; } if (keyStates == 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) { 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.