• XNA中的中文输入(二)


     XNA中的中文输入(二)

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

     

             全屏模式下,由于GDIDirectX会发生冲突,我们需要自己渲染IME窗口。很多人都觉得渲染IME窗口是件很复杂的事情,但仔细观察一下CustomUI或者WOW中的IME窗口,其实就是一个简单的text或者label控件而已!


          只要得到IME中的字符信息,接下来就很简单了.为此,我们需要处理以下4个消息:

             第一个是WM_IME_STARTCOMPOSITION,这个消息在按下第一个字符,开始一次新的字符组合时触发,可以把类似清除字符串buffer的工作放在这里。

             接下来,WM_IME_COMPOSITION是一个很重要的消息。SDK文档里说“The IMM sends a WM_IME_COMPOSITION message to the application when the user enters a keystroke to change the composition string.lParam参数说明IME发生了什么样的变化,这里我们只需关心GCS_COMPSTR或者GCS_COMPREADSTR,对于中文来说,这两个标志所表示的内容都是一致的,表示输入字符发生了变化。可以用ImmGetCompositionString获得此时的IME输入字符。

             IME窗口发生变化时,向程序发送WM_IME_NOTIFY消息,wParam参数说明发生了什么变化。一般来说只需要关心IMN_CHANGECANDIDATE,它表示IME窗口中的候选字符发生了变化。此时需要用ImmGetCandidateList获得候选字符。

             最后,当完成输入时,处理WM_IME_ENDCOMPOSITION消息,同样使用ImmGetCompositionString获得最终生成的中文字符。

             基本的步骤就那么简单,下面是一些实现细节,主要是P/Invoke时可能遇到的问题。首先是两个函数的声明。

     [DllImport("imm32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
     
    public static extern int ImmGetCompositionString(IntPtr hIMC, int CompositionStringFlag, StringBuilder buffer, int bufferLength);

     [DllImport(
    "imm32.dll", CharSet = CharSet.Unicode, EntryPoint = "ImmGetCandidateList")]
     
    public static extern uint ImmGetCandidateList(IntPtr hIMC, uint deIndex, IntPtr candidateList, uint dwBufLen);

             注意,必须显式指定CharSet属性,否则导入的总是Ansi版本的API

             先看ImmGetCompositionString函数,它的第一个参数是input context句柄,第二个参数表示我们希望取得IME中的哪种字符串。一般来说在WM_IME_COMPOSITION时,应该使用GCS_COMPSTRGCS_COMPREADSTR,在WM_IME_ENDCOMPOSITION时使用GCS_RESULTSTR。函数将把得到的字符串放在第三个参数中。最后则是字符串长度,如果这个值为0,则返回字符串的长度,因此你可能先查询字符串长度,然后在取字符串。

             ImmGetCandidateListImmGetCompositionString的用法非常相似。这里比较有技术性的地方在于返回的CandidateList参数。首先需要为这个结构做以下声明:

    CandidateList
    [StructLayoutAttribute(LayoutKind.Sequential)]
    public struct CandidateList
    {
        
    public uint dwSize;
        
    public uint dwStyle;
        
    public uint dwCount;
        
    public uint dwSelection;
        
    public uint dwPageStart;
        
    public uint dwPageSize;
        
    /// DWORD[1]
        [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType =UnmanagedType.U4)]
        
    public uint[] dwOffset;
    }

              
            CandidateList保存了IME中的候选字符串,但仔细看CandidateList的成员,似乎没有任何一个与字符串相关。文档中的说明也只会让你更迷糊。这是一个非常特殊的结构,以下是当有4个候选字符串时,CandidateList在内存中的布局:

     swSize|dwStyle|…….|dwPageSize|dwOffset|Offset1|Offset2|Offset3|string0|string1|string2|string3

             可以看到,所有字符串信息实际上附加在dwOffset后面,dwOffset记录了第一个字符串相对于CandidateList的偏移值,如果有n个字符串,则后面会有n-132位的值,分别表示第n个字符串相对于对CandidateList的偏移值。在这些32位的值之后,则是每个字符串实际的值。也就是说dwOffset记录了string0的偏移值,offset3记录了string3的便宜值。注意,当swSize1时,CandidateList又是另外一种布局,具体可参考sdk文档。显然,只有直接访问物理地址,才能得到每个字符串。如何在C#里访问物理地址呢?以下是解析CandidateList的代码:

    if(m.Msg == WindowMessage.ImeNotify)
    {
        
    if(m.WParam.ToInt32() == IMN_CHANGECANDIDATE )
        {
            CandidateList candidate;
            IntPtr ptr;
            
    uint size = IMM.ImmGetCandidateList(imeContext, 0, IntPtr.Zero, 0);
            
    if(size > 0)
            {
                ptr 
    = Marshal.AllocHGlobal((int)size);
                size 
    = IMM.ImmGetCandidateList(imeContext, 0, ptr, size);
                candidate 
    = (CandidateList)Marshal.PtrToStructure(ptr, typeof(CandidateList));
                
    if(candidate.dwCount > 1)
                {
                    
    for (int i = 0; i < candidate.dwCount; i++)
                    {
                        
    int stringOffset = Marshal.ReadInt32(ptr, 24 + 4 * i);
                        IntPtr addr 
    = (IntPtr)(ptr.ToInt32() + stringOffset);
                        
    string str = Marshal.PtrToStringUni(addr );
                        Console.WriteLine(str);
                     }
                }
                
    else......
                Marshal.FreeHGlobal(ptr);
            }
        }
    }

           现在已经可以成功调用IME,并且获得IME中的字符了。下一次,我们将讨论如何在XNA的Game框架下使用IME。

    更新12.28.09:

       在Vista和Windows 7下,ms用Text Service Framework取代了IMM,虽然大部分IMM API仍然可用,但由于TSF内部的原因,可能有一些兼容性问题。更好的方法是直接使用TSF。

  • 相关阅读:
    [Maid] Write Tasks in Markdown with Maid
    [React] Preview and edit a component live with React Live
    [React] Build a slide deck with mdx-deck using Markdown + React
    [React] Spread Component Props in JSX with React
    重载new delete操作符是怎么调用的
    oracle如何设置show parameter显示隐含参数
    Google用户登录界面 Android实现
    Jquery 动态生成表单 并将表单数据 批量通过Ajax插入到数据库
    消息机4_B
    jQuery中对 input 控件的操作
  • 原文地址:https://www.cnblogs.com/clayman/p/1626812.html
Copyright © 2020-2023  润新知