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 IntPtrdesc); [ 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 上几行叙述,希望笔者此篇文章能多少帮助诸位,
少走一些冤枉路,下次再见了!
范例下载: http://code6421.myweb.hinet.net/Old/VistaIME/VistaIMEHelperNet.zip
原文地址:https://www.cnblogs.com/lonelyxmas/p/9186485.html