Clayman's Graphics Corner

DirectX,Shader & Game Engine Programming

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

 XNA中的中文输入(一)

仅供个人学习使用,请勿转载,勿用于任何商业用途。

 

         游戏中的中文输入一直是个很棘手的问题,就连wow也实现的不是很好,很多人不得不在窗口最大化模式下游戏。对大部分程序员来说,如果不开发输入法,永远也不需要关心IME是如果工作的,即使是游戏开发,除了中日韩等少数使用多字符语言的国家,也不需要关心IME。了解游戏IME开发的人,也因为种种原因,不愿过多讨论,因此相关资料非常之少。

     最近花了一周时间,终于实现了中文输入。本文主要讨论如何在XNA中调用IME,有些特定WinForm下的行为也许和传统DirectX程序不太一样。本文不会给出详细的实现代码和原理,但会把所有关键步骤都写出来。

       继续阅读之前,你应该先了解或准备以下资料:

1. DirectX SDKCustomeUIsample。虽然这并不是一个很适合“学习”的例子(代码太过于复杂),但毕竟是ms建议的标准实现方法,也是最兼容性最好的实现。

2.  DirectX SDK中的Using an Input Method Editor in a Game一文,以下简称“指南”。在我看来,这同样也是一篇不太好的指南,很多关键部分讲的非常模糊。

3.  Windows SDKIME相关的文档,可在MSDN—Win32 and COM—User Interface—Internationalization—SDK Document—International Text Display找到。

4. 对于XNA开发者还需要对windows消息机制(WindowProc)以及P/Invoke有所了解。

         我们要做的事情非常简单,1,在游戏程序中打开IME窗口;2,获得IME所“合成”的文字;3,隐藏IME窗口(可选)。实现的思想也很简单:拦截IME相关的windows消息,并进行处理就可以了。 

         要处理的第一个消息是WM_IME_SETCONTEXT(请先了解文档里Input Cotext的概念)SDK里说“Sent to an application when a window is activated”,按照我的理解,这句话的意思是每次窗口active之后,向窗口发送一次。但我在WinForm下的测试结果是很多IME状态改变都会触发这个消息。SDK里还说“By default, the operating system creates and assigns an input context to each thread”,这句话迷惑了我很久,从后来的实验来看,系统并没有“assigns an input context to each thread”,因为如果在接收到WM_IME_SETCONTEXT时,显式调用ImmAssociateContext,再Ctrl+shift,运气好的话,此时打字会发现IME窗口已经出现了!对,就这么简单(指南里竟然完全没有提到ImmAssociateContext这个函数,怨念啊,我苦苦google了一整个下午T_T)。为什么要说运气好呢?因为此时你见到IME窗口的可能性,基于你安装了哪种输入法!搜狗,恭喜你中奖了;紫光,你有50%的可能性看到;微软或者google拼音,基本上你什么都看不到。无论如何,总算是有了进展。让我讲的更详细一些,以便你能重现这个伟大的时刻J

1. 创建一个新的winForm程序,对我们先从winForm开始,然后再讨论XNA

2. 声明以下API

class IMM
{
   [DllImport(
"imm32.dll", SetLastError = true)]
   
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
}

 

3. 为了方便使用API,还需要声明以下常量

代码
[Flags]
public enum CompositionStringFlag
{
    ReadingString 
= 0x0001,
    ReadingStringAttribute 
= 0x0002,
    ReadingClause 
= 0x0004,
    CompositionString 
= 0x0008,
    CompositionStringAttribute 
= 0x0010,
    CompositionStringClause 
= 0x0020,
    CursorPosition 
= 0x0080,
    DeltaStart 
= 0x0100,
    ResultReadingString 
= 0x0200,
    ResultReadingStringClause 
= 0x0400,
    ResultString 
= 0x0800,
    ResultClause 
= 0x1000
}

public static class WindowMessage
{
    
public const int Char = 0x0102;
    
public const int ImeStartCompostition = 0x010D;
    
public const int ImeEndComposition = 0x010E;
    
public const int ImeComposition = 0x010F;
    
public const int ImeKeyLast = 0x010F;
    
public const int ImeSetContext = 0x0281;
    
public const int ImeNotify = 0x0282;
    
public const int ImeControl = 0x0283;
    
public const int ImeCompositionFull = 0x0284;
    
public const int ImeSelect = 0x0285;
    
public const int ImeChar = 0x286;
    
public const int ImeRequest = 0x0288;
    
public const int ImeKeyDown = 0x0290;
    
public const int ImeKeyUp = 0x0291;
    
public const int InputLanguageChange = 0x0051;
}

 

4. 为窗口添加以下函数:

 

代码
protected override void WndProc(ref Message m)
{
    
if (m.Msg == WindowMessage.ImeSetContext)
    {
        
if (m.WParam.ToInt32() == 1)
        {
            IntPtr imeContext 
= IMM.ImmGetContext(this.Handle);
            IMM.ImmAssociateContext(
this.Handle, imeContext);
        }
    }
    base.WndProc(ref m);
}

         好了,编译运行。成功没有?没有!对,就算你装了先前提到的第一种输入法,此时程序也不会有任何反应。Why?设置断点,你会发现imeContext的值是0,意味着根本没有得到正确的context。再仔细Debug,你会发现在程序启动时,WM_IME_SETCONTEXT消息被触发了不止一次,第一次消息触发时,正确得到了context,但后面的几次消息,只会返回0。所以,必须在初始化时,保存对context的引用,这是与CustomeUI例子非常不同的一点,猜想也许是winForm底层机制的原因。再次运行,你应该能看到些东西了。

         现在来解决只对搜狗和紫光有效的问题(顺便说一下,之前一直觉得各种输入法之间没多大差别,这次才发现搜狗是所有输入法里最神奇的)。这是真正困扰了我一周的问题,结果却出乎意料,只需要在WndProc里添加以下代码:

 

if (m.Msg == WindowMessage.InputLanguageChange)
{
    
return//Don't pass this message to base class!!!!
}

         不出意外的话,现在你已经可以调出所有输入法了。下一次,我将讨论如何取出IME中的字符

 

 

posted on 2009-12-17 03:21  clayman  阅读(4372)  评论(3编辑  收藏  举报