zoukankan      html  css  js  c++  java
  • XNA中的中文输入(一)

     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中的字符

     

     

  • 相关阅读:
    mapreduce参数记录
    find命令使用中记录
    运算符的优先级(从高到低)
    常用字符与ASCII代码对照表
    mysql在liunx上使用记录
    Java 流收集器 ( Stream Collectors )
    CDH 删除Agent节点(退役节点)
    学习记录CombineFileInputFormat类
    读取HBases的数据的三种常见用法
    hash算法学习
  • 原文地址:https://www.cnblogs.com/clayman/p/1626084.html
Copyright © 2011-2022 走看看