VISTA 与输入法程式介面

简介: 原文:VISTA 与输入法程式介面 VISTA 与输入法程式介面       文/黄忠成      近日,我所兼职顾问的公司开始将旧有的Win32 程式及新开发的.NET 应用程式移转到VISTA 系统上测试,由于我们的应用程式多半是商用套装软体, 相当然尔对于以程式切换输入法的...

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

VISTA 与输入法程式介面

 
 
 
文/黄忠成
 
    近日,我所兼职顾问的公司开始将旧有的 Win32  程式及新开发的 .NET  应用程式移转到 VISTA  系统上测试,由于我们的应用程式多半是商用套装软体,
相当然尔对于以程式切换输入法的需求是一定存在的,对于客户来说,在焦点移往该输入中文的栏位时,由系统自动为其切换适当的输入法是种便利的设计!
只是这些原本在 Windows XP/2000/2003  上运作的相当正常的程式,到了 VISTA  后,却不约而同出现了同样的问题,那就是自动切换输入法的功能全部失效了,
这不只出现在旧有的 Win32  应用程式,连新开发的 .NET Framework 2.0  应用程式也无法幸免!当工程师们向我询问关于此问题的解决办法时,我直觉的认为,
这可能是 VISTA  在输入法的程式介面上做了变动,也就是旧有的 API  已经失去功能,由另外一种介面来取代了!只是,我毫无头绪,不知该如何去找出这个
新介面是什么,更别谈说提出一个可以解决此问题的办法了。我与多数设计师一样,立刻就打开 google  ,企图在搜寻引擎上找到一点蛛丝马迹,
很不幸的! google  上找不到任何有关此问题的线索,在这种情况下,我想到了 .NET Framework 3.  ,这是目前最新的.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 
此函式会传回目前系统作用中的输入法名称,这有何用呢?一般来说,在设计自动输入法切换时,会有两种模式,一是要求使用者选择一种输入法做为主要输入法,
当焦点所在栏位需要输入中文时,系统自动切换至此输入法。另一种模式是是不硬性要求使用者选择输入法,而是以最近所切换的输入法为准,在这种模式下,
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  上几行叙述,希望笔者此篇文章能多少帮助诸位,
少走一些冤枉路,下次再见了!
 

 

目录
相关文章
|
7月前
|
Windows
windows11 删除输入法
windows11 删除输入法
71 0
|
Windows
使用PowerShell获取Windows当前锁屏壁纸
使用PowerShell获取Windows当前锁屏壁纸 如果原始图片丢了,用这段代码就可以提取当前锁屏壁纸了!
183 0
|
分布式计算 Linux Android开发
这 8 款 Windows 屏保程序,让你的锁屏都格调满满
这 8 款 Windows 屏保程序,让你的锁屏都格调满满
1008 0
这 8 款 Windows 屏保程序,让你的锁屏都格调满满
|
Windows
Win系统 - 不会读的字也能打出来!Windows自带输入法原来超好用~
Win系统 - 不会读的字也能打出来!Windows自带输入法原来超好用~
183 0
Win系统 - 不会读的字也能打出来!Windows自带输入法原来超好用~
|
Windows
Win系统 - Windows10 玩游戏卡?试试这招
Win系统 - Windows10 玩游戏卡?试试这招
112 0
Win系统 - Windows10 玩游戏卡?试试这招
|
API C# Windows
c# windows程序调用本地输入法
原文:c# windows程序调用本地输入法    好久没写博客了,今天写了一个DEMO,在WINform程序中调用本地输入法,并在窗体中显示出来。
1415 0