VISTA 与输入法程式介面

原文:VISTA 与输入法程式介面

VISTA 与输入法程式介面

 
 
 
文/黄忠成
 
    近日,我所兼职顾问的公司开始将旧有的 Win32  程式及新开发的 .NET  应用程式移转到 VISTA  系统上测试,由于我们的应用程式多半是商用套装软体,
相当然尔对于以程式切换输入法的需求是一定存在的,对于客户来说,在焦点移往该输入中文的栏位时,由系统自动为其切换适当的输入法是种便利的设计!
只是这些原本在 Windows XP/2000/2003  上运作的相当正常的程式,到了 VISTA  后,却不约而同出现了同样的问题,那就是自动切换输入法的功能全部失效了,
这不只出现在旧有的 Win32  应用程式,连新开发的 .NET Framework 2.0  应用程式也无法幸免!当工程师们向我询问关于此问题的解决办法时,我直觉的认为,
这可能是 VISTA  在输入法的程式介面上做了变动,也就是旧有的 API  已经失去功能,由另外一种介面来取代了!只是,我毫无头绪,不知该如何去找出这个
新介面是什么,更别谈说提出一个可以解决此问题的办法了。我与多数设计师一样,立刻就打开 google  ,企图在搜寻引擎上找到一点蛛丝马迹,
很不幸的! google  上找不到任何有关此问题的线索,在这种情况下,我想到了 .NET Framework 3.  0  ,这是目前最新的.NET Framework版本,或许里面
已经使用到了这个新的API,但测试的结果仍然是一样,原本于.NET Framework的Windows Form应用程式中,我们可以利用以下的程式码来列出系统
中所安装的输入法。

 

public void GetLanguages()
{
   (在InputLanguage.InstalledInputLanguages中的InputLanguage lang)
{
      textBox1.Text + = lang.Culture.EnglishName +'\ n';
   }
}
基本上,此方法通用于.NET Framework 1.0/1.1/2.0/3.0,在Windows XP/2000/2003上都可以正常运作,但在VISTA下,这个方式只能列出该系统所安装的语言,
而非输入法!事实上,这个物件是利用Windows API:GetKeyboardLayoutList函式来取得输入法列表,而此函式目前看来,已经无法在VISTA上正常运作了。
既然在Windows Form Framework下无法找到线索,我转往新的Framework:Windows Presentation Foundation,也就是WPF!这是Windows最新的UI介面,
总该有些线索了吧?答案很令我意外,WPF中虽然也存在着InputLanguageManager物件,但一样也只能列出系统所安装的语言,无法进一步的列出输入法。
最后!我将脑筋动到了正处于Beta的.Net Framework 3.5上,虽然结果仍然相同,但于其中我发现了一个Framework的踪迹,那就是TSF(Text Service Framework),
看来!在VISTA中的Imm32.dll(用来管理、切换输入法介面所在的DLL)所有功能皆已被此Framework完全取代。既然已经找到了一点蛛丝马迹,接下来就只要搞清楚
TSF的设计概念及使用方式,就能够解决当下所遭遇到的问题了。TSF是一组以COM物件组成的Framework,主要目的在提供更具延展性、安全性的语言服务,
与旧有的Imm32.dll以输入法为中心的设计不同,TSF一开始就设计成可于单一系统中安装多个语言,而每个语言可以拥有多个输入法,从此点看来,在以多语言
支援所设计的VISTA环境下,Imm32.dll会失效的理由就不难理解了。好了!这就是前半部的探索过程,现在就让我们进入问题的核心,TSF所提供的功能相当多,
但目前我们只需要列出输入法、切换输入法这些功能,所以本文就将焦点集中于此,待日后有机会再与读者们分享TSF其它的运用。
 
 
与TSF相遇,列出特定语言下的输入法
 
 
  在现在所能找到的TSF资讯,皆是以C++做为基准所撰写的,所有范例也都以C++来撰写的,因此要于.NET中运用TSF的话,首先得先将Windows SDK中所提供
的TSF C++ Header file以P/Invoke方式宣告成.NET语言可用的格式,本文中以C#为例,如下所示:
 
[msctf.cs]

 

///
// Microsoft文本服务框架声明
//从C ++头文件
//
//
使用 系统;
使用 System.ComponentModel;
使用 System.Collections.Generic;
使用 System.Text;
使用 System.Runtime.InteropServices;
使用 System.Security;
 
命名空间 TSF
{
    [  StructLayout ( LayoutKind  .Sequential)]
     内部  结构  TF_LANGUAGEEPROFILE
    {
         内部  Guid  clsid;
         内部  短  LANGID;
         内部  Guid  catid;
        [  MarshalAs ( UnmanagedType  .Bool)]
         内部  布尔 功能;
         内部  Guid  guidProfile;
    }
 
    [  ComImport , SecurityCritical , SuppressUnmanagedCodeSecurity ,
Guid  ( “1F02B6C5-7842-4EE6-8A0B-9A24183A95CA” ),
      InterfaceType ( ComInterfaceType  .InterfaceIsIUnknown)]
     内部  接口  ITfInputProcessorProfiles
    {
        [  SecurityCritical  ]
         void  Register();  //非执行!可能是错误的声明。
        [  SecurityCritical  ]
         void  Unregister();  //非执行!可能是错误的声明。
        [  SecurityCritical  ]
         void  AddLanguageProfile();  //非执行!可能是错误的声明。
        [  SecurityCritical  ]
         void  RemoveLanguageProfile();  //非执行!可能是错误的声明。
        [  SecurityCritical  ]
         void  EnumInputProcessorInfo();  //非执行!可能是错误的声明。
        [  SecurityCritical  ]
         int   GetDefaultLanguageProfile( short  langid, ref  Guid  catid, out  Guid  clsid, out  Guid  profile);
        [  SecurityCritical  ]
         void  SetDefaultLanguageProfile();  //非执行!可能是错误的声明。
        [  SecurityCritical  ]
         int  ActivateLanguageProfile( ref  Guid  clsid, short  langid, ref  Guid  guidProfile);
        [  PreserveSig , SecurityCritical  ]
         int  GetActiveLanguageProfile( ref  Guid  clsid, out  short  langid, out  Guid  profile);
        [  SecurityCritical  ]
         int  GetLanguageProfileDescription( ref  Guid  clsid, short  langid, ref  Guid  profile, out  IntPtr desc);
        [  SecurityCritical  ]
         void  GetCurrentLanguage( out  short  langid);  //非执行!可能是错误的声明。
        [  PreserveSig , SecurityCritical  ]
         int  ChangeCurrentLanguage( short  langid);  //非执行!可能是错误的声明。
        [  PreserveSig , SecurityCritical  ]
         int  GetLanguageList ( out  IntPtr  langids, out  int  count);
       [  SecurityCritical  ]
         int  EnumLanguageProfiles( short  langid, out  IEnumTfLanguageProfiles  enumIPP);
        [  SecurityCritical  ]
         int  EnableLanguageProfile();
        [  SecurityCritical  ]
         int  IsEnabledLanguageProfile( ref  guid  clsid, short  langid, ref  Guid  profile, out  bool  enabled);
        [  SecurityCritical  ]
         void  EnableLanguageProfileByDefault();  //非执行!可能是错误的声明。
        [  SecurityCritical  ]
         void  SubstituteKeyboardLayout();  //非执行!可能是错误的声明。
    }
 
    [  ComImport , InterfaceType ( ComInterfaceType  .InterfaceIsIUnknown),
  Guid ( “3d61bf11-ac5f-42c8-a4cb-931bcc28c744” )]
     内部  接口  IEnumTfLanguageProfiles
    {
         无效 克隆( 输出  IEnumTfLanguageProfiles  enumIPP);
        [  PreserveSig  ]
         int  Next( int  count,[  Out , MarshalAs ( UnmanagedType  .LPArray,SizeParamIndex = 2)]
TF_LANGUAGEPROFILE  [] profiles, out  int  fetched);
         无效 重置();
         无效 跳过( int  count);
    }
 
     内部  静态  类  TSF_NativeAPI
    {
         公共  静态  只读  guid  GUID_TFCAT_TIP_KEYBOARD;
 
         静态  TSF_NativeAPI()
        {
            GUID_TFCAT_TIP_KEYBOARD =  新的  Guid (0x34745c63,0xb2f0,
0x4784,0x8b,0x67,0x5e,0x12,200x,0x1a,0x31);
        }
 
        [  SecurityCritical , SuppressUnmanagedCodeSecurity , DllImport ( “msctf.dll” )]
         public  static  extern  int  TF_CreateInputProcessorProfiles( out  ITfInputProcessorProfiles  profiles);
    }
}
OK  !我知道,这段程式码对于不熟悉COM、P/Invoke的读者而言,就像是无字天书般难懂,不过请放心,我们后面会再撰写一个Wrapper物件,
简化使用TSF的过程。在这个程式码中,有几个函式值得注意,第一个就是GetLanguageList,她可以列出系统中所安装的语言,并传回一个
LANGID型别的阵列,一般来说,预设的语言会排在阵列中的第一个,透过LANGID,我们就能够呼叫另一个函式:EnumLanguageProfiles
来取得该语言下所安装的输入法了,如下例所示:
 
[TSFWrapper.cs]

 

public  static short [] GetLangIDs()
{
        List  <  short  > langIDs =  new  List  <  short  >();
        ITfInputProcessorProfiles 配置文件;
        如果 ( TSF_NativeAPI  .TF_CreateInputProcessorProfiles( out  profiles)== 0)
      {
            IntPtr  langPtrs;
            int  fetchCount = 0;
            if (profiles.GetLanguageList( out  langPtrs, out  fetchCount)== 0)
           {
                for ( int  i = 0; i <fetchCount; i ++)
               {
                    short  id =  Marshal  .ReadInt16(langPtrs, sizeof ( short )* i);
                   langIDs.Add(ID);
               }
           }
            Marshal  .ReleaseComObject(profiles);
       }
        返回  langIDs.ToArray();
}
 
public  static string [] GetInputMethodList(short langID)
{
      List  <  string  > imeList =  new  List  <  string  >();
      ITfInputProcessorProfiles 配置文件;
     如果 ( TSF_NativeAPI  .TF_CreateInputProcessorProfiles( out  profiles)== 0)
     {
          尝试
         {
              IEnumTfLanguageProfiles  enumerator =  null  ;
              if (profiles.EnumLanguageProfiles(langID, out  enumerator)== 0)
             {
                 if (enumerator!=  null )
                {
                      TF_LANGUAGEPROFILE  [] langProfile =  new  TF_LANGUAGEPROFILE  [1];
                      int  fetchCount = 0;
                      while (enumerator.Next(1,langProfile, out  fetchCount)== 0)
                     {
                                 IntPtr  ptr;
                                 if (profiles.GetLanguageProfileDescription( ref  langProfile [0] .clsid,
langProfile [0] .langid, ref  langProfile [0] .guidProfile, out  ptr)== 0)
                                {
                                     布尔 启用;
                                     if (profiles.IsEnabledLanguageProfile( ref  langProfile [0] .clsid,
 langProfile [0] .langid, ref  langProfile [0] .guidProfile, out  enabled)== 0)
                         {
                             如果 (启用)
                              imeList.Add( Marshal  .PtrToStringBSTR(ptr));
                         }
                      }
                      Marshal.FreeBSTR(ptr);
                 }
              }
          }
       }
        最后
      {
            Marshal  .ReleaseComObject(profiles);
       }
    }
     return  imeList.ToArray();
 }
上例是节录自TSFWapper,笔者所设计的TSF Wrapper物件,利用此物件,设计师可以在不了解TSF的情况下,取得输入法列表及切换输入法。
在使用上,设计师得先呼叫GetLangIDs函式来取得目前系统所安装的语言,再针对特定语言呼叫GetInputMethodList函式来取得所安装的输入法列表,
如下面的程式片段所示。

 

私人 短 [] langIDs;
………
private  void button1_Click(object sender,EventArgs e)
{
     langIDs =  TSFWrapper  .GetLangIDs();
      如果 (langIDs.Length> 0)
     {
          string  [] list =  TSFWrapper  .GetInputMethodList(langIDs [0]);
          的foreach ( 字符串 递减 的 列表)
           listBox1.Items.Add(降序);
      }
}
 
下面是此范例于VISTA上运行的画面。
 
与TSF  相遇II  ,切换输入法
 
  在可以取得输入法列表后,接下来的工作当然是实作切换输入法的功能了,在前面的msctf.cs中,ActivateLanguageProfile函式就是作此用途,
同样的,TSFWrapper物件中也实作了简单的函式来协助设计师完成此工作。

 

public  static bool ActiveInputMethodWithDesc(short langID,string desc)
{
     ITfInputProcessorProfiles 配置文件;
     如果 ( TSF_NativeAPI  .TF_CreateInputProcessorProfiles( out  profiles)== 0)
    {
        尝试
       {
           IEnumTfLanguageProfiles  enumerator =  null  ;
           if (profiles.EnumLanguageProfiles(langID, out  enumerator)== 0)
          {
               if (enumerator!=  null )
              {
                     TF_LANGUAGEPROFILE  [] langProfile =  new  TF_LANGUAGEPROFILE  [1];
                     int  fetchCount = 0;
                     while (enumerator.Next(1,langProfile, out  fetchCount)== 0)
                    {
                         IntPtr  ptr;
                         if (profiles.GetLanguageProfileDescription( ref  langProfile [0] .clsid,
 langProfile [0] .langid, ref  langProfile [0] .guidProfile, out  ptr)== 0)
                        {
                              布尔 启用;
                              if (profiles.IsEnabledLanguageProfile( ref  langProfile [0] .clsid,
langProfile [0] .langid, ref  langProfile [0] .guidProfile, out  enabled)== 0)
                             {
                                 如果 (启用)
                                {
                                     string  s =  Marshal  .PtrToStringBSTR(ptr);
                                     if (s.Equals(desc))
                                        返回  profiles.ActivateLanguageProfile( 参考  langProfile [0] .clsid,
 langProfile [0] .langid, ref  langProfile [0] .guidProfile)== 0;
                                 }
                              }
                               Marshal.FreeBSTR(ptr);
                         }
                       }
                   }
                }
            }
             最后
            {
                 Marshal  .ReleaseComObject(profiles);
            }
       }
        返回  false  ;
}
使用此函式的方法很简单,只需传入欲切换的输入法名称及语言(LANGID)即可。
 

 

private  void button2_Click(object sender,EventArgs e)
{
     如果 (langIDs!=  null )
    {
            if (listBox1.SelectedIndex!= -1)
                TSFWrapper  .ActiveInputMethodWithDesc(langIDs [0],( string )listBox1.SelectedItem);
     }
 }
 
 
与TSF暂别,取得现行输入法及关闭输入法
 
  能切过去, 也要能切回来,本文最后的工作就是得将输入法切回英文输入,在进入正题前,笔者先介绍 TSFWrapper  中的另一个函式: GetCurrentInputMethodDes  c  ,
此函式会传回目前系统作用中的输入法名称,这有何用呢?一般来说,在设计自动输入法切换时,会有两种模式,一是要求使用者选择一种输入法做为主要输入法,
当焦点所在栏位需要输入中文时,系统自动切换至此输入法。另一种模式是是不硬性要求使用者选择输入法,而是以最近所切换的输入法为准,在这种模式下,
GetCurrentInputMethodDesc就可以派上用场了。好了,回到正题来,在中文栏位切成中文输入法,在英文栏位时当然就得切回英数输入法了,
TSFWrapper提供了此函式。

 

public  static bool DeActiveInputMethod(short langID)
{
      List  <  string  > imeList =  new  List  <  string  >();
      ITfInputProcessorProfiles 配置文件;
      如果 ( TSF_NativeAPI  .TF_CreateInputProcessorProfiles( out  profiles)== 0)
    {
         尝试
        {
              Guid  clsid =  Guid  .Empty;
              返回  profiles.ActivateLanguageProfile( ref  clsid,langID, ref  clsid)== 0;
        }
         最后
       {
              Marshal  .ReleaseComObject(profiles);
        }
     }
      返回  false  ;
 }
 
后记
 
    TSF  目前所能取得的资讯相当的少,于 google  上讨论此课题的文章也极其稀少,仅只有 MSDN  上几行叙述,希望笔者此篇文章能多少帮助诸位,
少走一些冤枉路,下次再见了!
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值