Another approach using a Windows Hook instead of subclassing XNA's window http://www.gamedev.net/community/forums/topic.asp?topic_id=543581 http://www.benryves.com/index.php?module=journal&mode=filtered&single_post=3024426 ------------------------------------------------------------------------------------- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * XnaTextInput.TextInputHandler - benryves@benryves.com * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This is quick and very, VERY dirty. * * It uses Win32 message hooks to grab messages (as we don't get a nicely wrapped WndProc). * * I couldn't get WH_KEYBOARD to work (accessing the data via its pointer resulted in access * * violation exceptions), nor could I get WH_CALLWNDPROC to work. * * Maybe someone who actually knows what they're doing can work something out that's not so * * kludgy. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This quite obviously relies on a Win32 nastiness, so this is for Windows XNA games only! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #region Using Statements using System; using System.Runtime.InteropServices; using System.Windows.Forms; // This class exposes WinForms-style key events. #endregion namespace XnaTextInput { /// /// A class to provide text input capabilities to an XNA application via Win32 hooks. /// class TextInputHandler : IDisposable { #region Win32 /// /// Types of hook that can be installed using the SetWindwsHookEx function. /// public enum HookId { WH_CALLWNDPROC = 4, WH_CALLWNDPROCRET = 12, WH_CBT = 5, WH_DEBUG = 9, WH_FOREGROUNDIDLE = 11, WH_GETMESSAGE = 3, WH_HARDWARE = 8, WH_JOURNALPLAYBACK = 1, WH_JOURNALRECORD = 0, WH_KEYBOARD = 2, WH_KEYBOARD_LL = 13, WH_MAX = 11, WH_MAXHOOK = WH_MAX, WH_MIN = -1, WH_MINHOOK = WH_MIN, WH_MOUSE_LL = 14, WH_MSGFILTER = -1, WH_SHELL = 10, WH_SYSMSGFILTER = 6, }; /// /// Window message types. /// /// Heavily abridged, naturally. public enum WindowMessage { WM_KEYDOWN = 0x100, WM_KEYUP = 0x101, WM_CHAR = 0x102, }; /// /// A delegate used to create a hook callback. /// public delegate int GetMsgProc(int nCode, int wParam, ref Message msg); /// /// Install an application-defined hook procedure into a hook chain. /// /// Specifies the type of hook procedure to be installed. /// Pointer to the hook procedure. /// Handle to the DLL containing the hook procedure pointed to by the lpfn parameter. /// Specifies the identifier of the thread with which the hook procedure is to be associated. /// If the function succeeds, the return value is the handle to the hook procedure. Otherwise returns 0. [DllImport("user32.dll", EntryPoint = "SetWindowsHookExA")] public static extern IntPtr SetWindowsHookEx(HookId idHook, GetMsgProc lpfn, IntPtr hmod, int dwThreadId); /// /// Removes a hook procedure installed in a hook chain by the SetWindowsHookEx function. /// /// Handle to the hook to be removed. This parameter is a hook handle obtained by a previous call to SetWindowsHookEx. /// If the function fails, the return value is zero. To get extended error information, call GetLastError. [DllImport("user32.dll")] public static extern int UnhookWindowsHookEx(IntPtr hHook); /// /// Passes the hook information to the next hook procedure in the current hook chain. /// /// Ignored. /// Specifies the hook code passed to the current hook procedure. /// Specifies the wParam value passed to the current hook procedure. /// Specifies the lParam value passed to the current hook procedure. /// This value is returned by the next hook procedure in the chain. [DllImport("user32.dll")] public static extern int CallNextHookEx(int hHook, int ncode, int wParam, ref Message lParam); /// /// Translates virtual-key messages into character messages. /// /// Pointer to an Message structure that contains message information retrieved from the calling thread's message queue. /// If the message is translated (that is, a character message is posted to the thread's message queue), the return value is true. [DllImport("user32.dll")] public static extern bool TranslateMessage(ref Message lpMsg); /// /// Retrieves the thread identifier of the calling thread. /// /// The thread identifier of the calling thread. [DllImport("kernel32.dll")] public static extern int GetCurrentThreadId(); #endregion #region Hook management and class construction. /// Handle for the created hook. private readonly IntPtr HookHandle; private readonly GetMsgProc ProcessMessagesCallback; /// Create an instance of the TextInputHandler. /// Handle of the window you wish to receive messages (and thus keyboard input) from. public TextInputHandler(IntPtr whnd) { // Create the delegate callback: this.ProcessMessagesCallback = new GetMsgProc(ProcessMessages); // Create the keyboard hook: this.HookHandle = SetWindowsHookEx(HookId.WH_GETMESSAGE, this.ProcessMessagesCallback, IntPtr.Zero, GetCurrentThreadId()); } public void Dispose() { // Remove the hook. if (this.HookHandle != IntPtr.Zero) UnhookWindowsHookEx(this.HookHandle); } #endregion #region Message processing private int ProcessMessages(int nCode, int wParam, ref Message msg) { // Check if we must process this message (and whether it has been retrieved via GetMessage): if (nCode == 0 && wParam == 1) { // We need character input, so use TranslateMessage to generate WM_CHAR messages. TranslateMessage(ref msg); // If it's one of the keyboard-related messages, raise an event for it: switch ((WindowMessage)msg.Msg) { case WindowMessage.WM_CHAR: this.OnKeyPress(new KeyPressEventArgs((char)msg.WParam)); break; case WindowMessage.WM_KEYDOWN: this.OnKeyDown(new KeyEventArgs((Keys)msg.WParam)); break; case WindowMessage.WM_KEYUP: this.OnKeyUp(new KeyEventArgs((Keys)msg.WParam)); break; } } // Call next hook in chain: return CallNextHookEx(0, nCode, wParam, ref msg); } #endregion #region Events public event KeyEventHandler KeyUp; protected virtual void OnKeyUp(KeyEventArgs e) { if (this.KeyUp != null) this.KeyUp(this, e); } public event KeyEventHandler KeyDown; protected virtual void OnKeyDown(KeyEventArgs e) { if (this.KeyDown != null) this.KeyDown(this, e); } public event KeyPressEventHandler KeyPress; protected virtual void OnKeyPress(KeyPressEventArgs e) { if (this.KeyPress != null) this.KeyPress(this, e); } #endregion } }