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