XNA中的中文輸入(三)
僅供個人學習使用,請勿轉載,勿用於任何商業用途。
把前兩部分的代碼移植到XNA Game中,最大的問題在於無法訪問底層的WinForm視窗,自然也就無法獲得視窗訊息。網上有一些通過window handle建立一個NativeWindow,然後重載NativeWindow WndProc的方法來獲得windos訊息。不幸的是把之前的代碼添加在這樣的構架中,不會有任何作用。順便說一下,Game把WinForm封裝到內部實在是一種不太好的策略。因此,我們不得不直接調用API hook視窗訊息。
首先,聲明以下函數:
代碼
[DllImport("user32.dll",CharSet = CharSet.Unicode)]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
建立一個靜態類來完成所有訊息捕獲任務:
代碼
public static class InputCaputure
{
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
static bool initialized;
static IntPtr prevWndProc;
static WndProc hookProcDelegate;
static IntPtr hIMC;
public static void Initialize(GameWindow window)
{
if (initialized)
throw new InvalidOperationException("InputCaputure.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)
{
switch (msg)
{
case WindowMessage.ImeSetContext:
{
//add code here
return (IntPtr)1;
}
case WindowMessage.ImeStartCompostition:
//add code here
return (IntPtr)0;
case ............
default:
return CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam);
}
}
}
好了,現在把之前的代碼搬到HookProc中,它將捕獲並處理所有我們關心的訊息,之後,再把所有訊息傳遞給Game。
移植完所有代碼之後,你會發現幾個問題。首先,雖然成功啟用了IME視窗,但IME似乎接收不到任何按鍵訊息,添加以下代碼:
case WM_GETDLGCODE:
return (IntPtr)DLGC_WANTALLKEYS;
其次,系統預設的IME視窗不會顯示了。很好,這正是我們希望的效果。此外,在IME啟用的狀態下,一直按著鍵盤鍵盤,會導致遊戲被卡住。這是hook視窗訊息得到的bug嗎?不是,做個實驗,wow也會這樣。最後,如果在全螢幕模式下,還會出現一些問題,這主要是看是否在正確的位置,調用了Initialize。
通過這三篇文章的,你應該已經能夠為遊戲編寫一個最基本的中文輸入系統了。最後還有一些函數是你應該留意的:
1. 除了在捕獲WM_IME_ENDCOMPOSITION用ImmGetCompositionString獲得合成的字串外,捕獲WM_IME_CHAR和WM_CHAR也能獲得同樣的效果。
2. GetKeyboardLayout可以獲得與當前鍵盤配置相關的訊息。
3. ImmGetIMEFileName可以獲得當前IME的標識名稱。
4. ImmSetConversionStatus可以切換半形全形狀態。
5. 用WM_CHAR處理普通輸入,而不要用Xna中的Keyboard。因為XNA中的Keyboard本來就不是為輸入字元設計的,你根本無法獲得Caps Lock等狀態,因此,即使是普通英文輸入,也非常麻煩。