再问个,在状态栏中想加入输入法,行吗? 比如我又新添加了一项ID_ABCDEFG,在indicators中单击ID_ABCDEFG时,就切换输入法哪位兄弟有经验,指导一下啊 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 先我们需要明白输入法是什么东西。目前常用的输入法基本上有两种类型:外挂式(如早期的万能五笔)及输入法接口式(Input Method Editor-IME)。外挂式比较简单,就是一个exe文件,通过模拟一些Windows输入消息来给当前处于活动状态的编辑窗口输入文字,一个显著的优点是输入法只要启动一次,就可以在所有进程中使用;但缺点不不容忽视,首先实现起来也不容易,一个更大的不足是兼容性不够好,通常一个Windows版本需要一人对应的输入法版本,此外这类输入法为了能够截获用户输入,通常需要挂接键盘钩子,容易造成系统不稳定或者效率不高。大部分的输入法还是采用IME来实现,下面本文主要讨论一下IME编程需要注意的问题及解决办法。IME是什么?IME是在Windows平台上使用的标准的输入法接口规范。它实质是一个DLL,Windows为这个DLL定义一系列的接口,不同的接口实现指定的功能。程序员在编写输入法程序时只需要实现这些接口并导出就可以作为输入法使用。关于具体接口的定义不是本文的重点,如果您需要了解只需要在网络中搜索“输入法编程指南”就可以明白 ,更多信息参考MSDN。刚开始输入法编程最棘手的问题通常是程序框架搭好了却不知道如何使用及调试。这里涉及到一个很重要的问题就是输入法的安装。输入法就是Windows的一个插件,需要先进行注册,Windows才能识别并使用。为此您需要先将您生成的DLL复制到系统目录(Windows\System32)再调用API ImmInstallIME就可以实现了,在我的实践中是先编一个简单的程序来做安装工作,在每次输入法重新编译完成以后调用一次以完成输入法的注册。这里还有一个需要注意的问题是:Windows提供了一种机制,它允许输入法程序一旦启动就就不再退出,这就意味着如果你的程序代码经过修改需要重新安装时将不得不重新启动电脑。在IME定义的接口中有一个接口是提供IME的初始化的,它就是BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR lpszOption),下面的代码来自我写的输入法:BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR lpszOption){ lpIMEInfo->dwPrivateDataSize = sizeof(CONTEXTPRIV);//系统根据它为INPUTCONTEXT.hPrivate分配空间 lpIMEInfo->fdwProperty = IME_PROP_KBD_CHAR_FIRST |#ifdef _UNICODE IME_PROP_UNICODE |#endif IME_PROP_SPECIAL_UI | IME_PROP_END_UNLOAD ; lpIMEInfo->fdwConversionCaps = IME_CMODE_FULLSHAPE | IME_CMODE_NATIVE; lpIMEInfo->fdwSentenceCaps = IME_SMODE_NONE; lpIMEInfo->fdwUICaps = UI_CAP_2700; lpIMEInfo->fdwSCSCaps = 0; lpIMEInfo->fdwSelectCaps = SELECT_CAP_CONVERSION; _tcscpy(lpszUIClass,CLSNAME_UI); return TRUE;}lpIMEInfo->fdwProperty告诉Windows系统您编写的输入法的一些特征,注意一下IME_PROP_END_UNLOAD这个标志,有了它您编写的输入法会随着启动您的输入法的应用程序(如NotePad)的退出而退出,否则它将长驻于系统中,这也是为什么很多输入法在升级安装时需要首先重新启动电脑的原因。在这个接口中还有一点需要特别注意,那就是lpIMEInfo->dwPrivateDataSize,至少我是经过很多次测试才基本证实Windows根据该值为INPUTCONTEXT.hPrivate分配空间。此外如果您修改了这个接口,按照我个人的经验是需要重新调用ImmInstallIME来安装。在安装完成后,在输入法列表中应该已经有了您自己的输入法。点击调试,由于它是一个DLL,您需要先选择一个宿主程序,一般选择“记事本”,以调试方式启动“记事本”后,在这个“记事本”中打开您的输入法,您就可以在源代码中设置断点了。需要说明的是,VC6.0调试DLL不太好用,首先需要打上SP5或者SP6,这样也不能够在DLL启动的时候就设置断点,推荐使用.net来调试。输入法上下文(HIMC):HIMC是什么?在输入法编程时必然要接触到输入法上下文这个术语,刚接触时听起来实在是半懂不懂。由于输入法是一个插件,它需要和调用它的应用程序通讯,在输入法中生成的编码及重码信息保存在哪里应用程序才能正确的读取呢?答案就在于输入法上下文。输入法上下文是由User.exe(一个系统进程)为应用程序分配的内存句柄,在应用程序中启动的输入法在这块内存中写入数据,User.exe再将数据传递到应用程序。UIWnd:在IME中需要导出一个接口,原型如LRESULT WINAPI UIWndProc(HWND hUIWnd, UINT message,WPARAM wParam, LPARAM lParam),hUIWnd是由User.exe传过来的窗口句柄,它是输入法中创建的窗口如编码窗口,重码窗口,状态栏窗口的宿主(Owner),初学输入法编程的人可能会问这个窗口显示在哪里呢?其实它并不是一个普通的窗口,它只是一个用来传递Windows消息的窗口(Message Only),在使用时,您不需要关心它在哪里,只需要使用它就好了。一个IME需要导出19个(Win98版本)接口,但是对于一个只需要实现一般意义的文字输入的软件,您只需要实现几个基本的接口就可以让输入法正常工作了。下面逐一介绍一下这几个接口。/**********************************************************************//* ImeSelect() *//* Return Value: *//* TRUE - successful, FALSE - failure *//**********************************************************************/BOOL WINAPI ImeSelect(HIMC hIMC,BOOL fSelect)在这个接口中,系统通知输入法当前是否打开了输入法输入。一般输入法启动时会调用一次,在一些软件(如EmEditor)中提供打开与关闭输入法的功能就是通过这个接口实现的。如果打开输入法,一般会在这个接口中做一些数据的初始化工作。/***********************************************************************//*系统调用这个接口来判断IME是否处理当前键盘输入 *//*HIMC hIMC:输入上下文 *//*UINT uKey:键值 *//*LPARAM lKeyData: unknown *//*CONST LPBYTE lpbKeyState:键盘状态,包含256键的状态 *//*return : TRUE-IME处理,FALSE-系统处理 *//*系统则调用ImeToAsciiEx,否则直接将键盘消息发到应用程序 *//**********************************************************************/BOOL WINAPI ImeProcessKey(HIMC hIMC,UINT uKey,LPARAM lKeyData,CONST LPBYTE lpbKeyState)观察注释您可以看到在个接口是用来判断用户敲击的哪个键需要处理,哪个键又应该交给系统自己处理,如果输入法需要自己处理用户输入的键,则在这个接口中返回true,否则返回false。/****************************************************************************************************************//* function:应用程序调用这个接口来进行输入上下文的转换,输入法程序在这个接口中转换用户的输入 *//* UINT uVKey:键值,如果在ImeInquire接口中为fdwProperty设置了属性IME_PROP_KBD_CHAR_FIRST,则高字节是输入键值*//* UINT uScanCode:按键的扫描码,有时两个键有同样的键值,这时需要使用uScanCode来区分 *//* CONST LPBYTE lpbKeyState:键盘状态,包含256键的状态 *//* LPDWORD lpdwTransKey:消息缓冲区,用来保存IME要发给应用程序的消息,第一个双字是缓冲区可以容纳的最大消息条数 *//* UINT fuState:Active menu flag(come from msdn) *//* HIMC hIMC:输入上下文 *//* return : 返回保存在消息缓冲区lpdwTransKey中的消息个数 *//****************************************************************************************************************/UINT WINAPI ImeToAsciiEx (UINT uVKey,UINT uScanCode,CONST LPBYTE lpbKeyState,LPDWORD lpdwTransKey,UINT fuState,HIMC hIMC)这个接口可以说是输入法最重要的部分,程序员需要在这个接口中实现编码与重码的转换,转换完成或者显示在编码窗口及重码窗口,或者发送到应用程序。由于在这个接口中没有传入窗口句柄,如果通知输入法程序的窗口更新显示呢?当然我们可以使用全局变量,在此我个人推荐的方法是使用IME消息(没有什么道理),您将消息类型、参数保存到lpdwTransKey指示的缓冲区中,User.exe会根据消息类型做相应的处理并传递到UIWnd这个窗口中。那么如何输入文字呢?要输入文字需要3个消息配合使用,分别是WM_IME_STARTCOMPOSITION、WM_IME_COMPOSITION和WM_IME_ENDCOMPOSITION,它们分别指示开始输入编码,输入编码或者结果(视参数而异)及编码输入完成。在开始编写输入法的时候,为了省事,我的输入法在用户确定要输入一个重码时才连续调用这3个消息以向编码器中输入文字。由于WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION需要成对使用,这种方法可以确保它们配对。最初这种方式工作得很好,但是后来发现在一些软件中出现兼容性问题。如“智能五笔”在“遨游”中就存在这个问题,在“遨游”中的地址栏中打开“智能五笔”,当需要使用回退键来删除错误输入的编码时,会发现删除的不是编码窗口中的编码而是编辑器中的文字。这是因为类似“遨游”这类软件主动接管了按键输入如处理一些控制键,当它发现这些控制键不在WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION这两个消息之间时就自己处理控制键而不是先交给User.exe了。因此正确的流程应该是在开始输入编码时发送WM_IME_STARTCOMPOSITION,输入结束后发送WM_IME_ENDCOMPOSITION消息。/**********************************************************************//* UIWndProc() *//* IME UI window procedure *//**********************************************************************/LRESULT WINAPI UIWndProc(HWND hUIWnd, UINT message,WPARAM wParam, LPARAM lParam)这是一个非常重要的接口,基本上一它负责各种消息的传递。一般您需要在这个接口中根据不同的消息类型,实现输入法窗口(如编码窗口、重码窗口、状态栏窗口)的显示、隐藏及更新等操作。这个接口实现的功能可能非常复杂,视情况而异,在此就不做更加深入的说明了。在使用时可以参见示例工程。BOOL WINAPI ImeConfigure(HKL hKL,HWND hWnd, DWORD dwMode, LPVOID lpData)这是最后一个需要注意的接口,在显示输入法属性配置时会Windows会调用这个接口。基本的接口就介绍到这里,下面谈一谈我个人在编写输入法程序时遇到的一些问题或者发现的一些需要注意的地方。1、关于输入法窗口:阅读一些输入法的代码会奇怪,为什么输入法窗口在创建时需要指定WM_DISABLE属性呢?原来是因为如果不指定这个属性标志,在打开输入法时,会导致当前的应用程序失去输入焦点。但是指定了这个标志后,输入法窗口不能收到鼠标消息怎么办?解决的方法就在于WM_SETCURSOR这个消息。这个消息不管窗口是否可用,只要有鼠标在窗口内窗口都会收到。您可以在这个消息中模拟鼠标消息也可以选择调用SetCapture这个函数,这样窗口就可以收到鼠标消息了。2、关于窗口模式:使用了几种输入法后,你会发现,有的输入法的编码窗口和重码窗口是一个窗口,有的又是两个窗口,它们有什么区别?或者有的人会觉得这个问题很可笑,但是当您研究了一段输入法可能就会发现您也有类似的问题:因为在输入法的导出接口中关于用户界面的函数就有4个,其它3个分类对应3个窗口回调函数。事实上它们并没有本质的区别,关键在于您的输入法的使用范围。一些软件(如某些游戏)为了界面的整体美观,不希望用户在打开输入法时显示输入法自己的窗口,而是希望输入法按照它的意愿将输入法窗口需要显示的内容显示在它创建的窗口中,英文称之为IME Aware。由于我自己的输入法目标不是在游戏中使用,所在并没有按照这个规矩来管理输入法窗口,而是为了简化,将编码窗口和重码窗口显示的内容放到了一个窗口中。3、关于自定义消息:UIWndProc在WM_IME_NOTIFY中提供了一个IMN_PRIVATE,最初我理解为这个消息应该和WM_USER一样,当我需要不只一人自定义消息时只需要在这个ID的基础上增加值就好了。但事实是您定义的值可能是系统已经占用的(视Windows的版本而异),您能够使用的自定义消息应该只有这一个,为了指示多个消息类型,我使用的方法是在WM_IME_NOTIFY的LPARAM中进行区分。4、调试信息输出:一般编写输入法都不会使用MFC,为了输出调试信息,一般只能使用OutputDebugString这个API,在示例代码中的helper.c中我编写了一个模拟TRACE的函数Helper_Trace,您可以用这个函数来将调试信息输出到调试窗口。5、最后再谈一谈输入法类型:前面提到输入法分为外挂式和IME两种,但是目前一些输入法发展了第3种类型,那就是结合这两种类型的优点。例如拼音加加,启动拼音加加您会发现进程列表里会多一个拼音加加的服务进程,其实它才是拼音加加输入法的内核即数据处理部分。拼音加加的IME部分只是一个外壳,它提供传统的IME输入法一样的系统兼容性。在我的输入法中也采用了这种结构,使用内存文件映射及普通的Windows消息结合来实现两个进程间的通讯。您可以在我的输入法的源代码中找到进程间的通讯源代码及输入法代码。好象没有更多的经验可言,总之,输入法其实并不神秘,在我看来,只要能够在VC中跟踪代码,我就不相信我会搞不定它!一家之言,如果有什么错误,还请大家批评指正。http://www.vckbase.com/document/viewdoc/?id=1807有源码 楼上讲的是开发输入法,不错不过楼主似乎只要切换输入法就可以了吧,这里有篇文章在Windows系统中一般都安装了至少三种输入法,在输入数据时常常会切换输入法,虽然Windows系统提供了切换快捷健,但对输入工作还是带来了不少麻烦。如果在应用程序中为用户提供智能输入法自动切换,那么这样的应用程序就显得更加专业、更加具有竞争力。不知你可用过Access,在表数据输入时Access自动切换输入法,如某字段需要输入英文时自动切换到En输入状态,如另一字段需要输入中文自动切换到某中文输入状态。 本文将对如何在Windows应用程序中动态的控制输入法的技术进行探讨。在DELPHI中许多控件都有控制输入法的属性,用户在设计时只要设置好这个属性就可以了,但在VC中并不直接提供对输入法的控制,要在VC应用中实现这种功能必须调用Windows API。在本文中我将用一个类将与输入法操作有关的Windows API函数进行封装,读者可以直接将这个类导入项目工程中,通过操作这个类来实现对输入法的控制,这样更适合于面向对象的开发。 要想控制输入法,首先要解决的问题是如果获得系统已安装的输入法信息。在Windows平台下,每个安装的输入法都在注册表中注册了相关信息。在“HKEY_CURRENT_USER\keyboard layout\preload”键下就可以找到这些信息,键下由以1为基的递增数字做为值名(暂取名为数字号),其值的内容是一个由八个数字组成的字符串(暂取名为代号,如"e0040804"),其中左4位是设备代码(device identifier),右4位是语言代码(language identifier)。例如上面:左e004指智能ABC,右0804指大陆中文。在MSDN中对所有代码做了详细的说明,如感兴趣请浏览MSDN相关内容。另外要说明一点的是在Windows98版本中输入法注册信息与上面说明略有不同,它是将已安装的输入法的数字号做为…\Preload下面的子键,而Windows2000将数字号做为…\Preload键下的值。 通过读取注册表中的输入法信息,可以列出所有已安装的输入法,但得到的输入法信息只是一些让人难懂的数字串,如何将这些数字串翻译成易懂的文字说明呢?同样, 在HKEY_LOCAL_MACHINE:"System\CurrentControlSet\Control\Keyboard Layouts\"键下注册了这些信息,它的子键名为输入法代号(keyboard layout),内容为该输入法的ime文件,名称等信息。到此,我们已经了解了Windows系统控制输入法的原理知识,下面我们开始着手创建一个控制输入法的C++类,主要步骤如下: 1. 创建一个新类,新类名为:CInputLanguage 2. 新建一个保存输入法信息的结构。当加载系统已安装的输入法信息时,用一个此结构的链表来保存输入法信息。struct IL{ char ilID[15]; //输入法代号。 char szName[100];//输入法的说明文字。 IL* pNext;}; 3. 加入一个私有的成员变量 IL* m_pILHead; 4. 加入加载输入法列表信息的成员函数//此函数只针对Windows2000以上版本,如要在Windows98版本的代码请与笔者联系。BOOL CInputLanguage::LoadInputLanguage(){ HKEY hKey,hKey1; DWORD cp=16; char lp[15]; CString szID; CString szKeyName,szKeyName1; szKeyName = "Keyboard Layout\\Preload"; szKeyName1 = "System\\CurrentControlSet\\Control\\Keyboard Layouts\\"; int i=1; szID.Format("%d",i); DWORD lpT=REG_SZ; if(::RegOpenKey(HKEY_CURRENT_USER,szKeyName,&hKey)==ERROR_SUCCESS ) { While( ::RegQueryValueEx(hKey,szID,NULL,&lpT,(LPBYTE)lp,&cp) == ERROR_SUCCESS ) { CString szTempName; szTempName = szKeyName1 + (LPCTSTR)(LPTSTR)lp; if(RegOpenKey(HKEY_LOCAL_MACHINE,szTempName,&hKey1)==ERROR_SUCCESS ) { char lpD[100]; DWORD lpS=100; //DataSize if(RegQueryValueEx(hKey1,"Layout text",NULL,&lpT,(LPBYTE)lpD,&lpS)==ERROR_SUCCESS) { IL* p1,*p2; p1 = m_pILHead; p2 = new(IL); strcpy(p2->ilID,lp); strcpy(p2->szName,lpD); p2->pNext = NULL; if( p1 ) { while( p1->pNext ){ p1 = p1->pNext ; } p1->pNext = p2; } else { m_pILHead = p2; } } } ::RegCloseKey(hKey1); i++; szID.Format("%d",i); } } ::RegCloseKey(hKey); return (m_pILHead != NULL );} 5. 加入选择输入法成员函数BOOL CInputLanguage::SelectInputLanguage(IL *pIL){ if( !pIL ) return FALSE; HKL hkl; hkl=LoadKeyboardLayout(pIL->ilID,KLF_ACTIVATE);//装载输入法 if(hkl==NULL) return FALSE; else{ ActivateKeyboardLayout(hkl,KLF_SETFORPROCESS);//激活输入法 } return TRUE;} 6. 其它部分CInputLanguage::CInputLanguage(){ m_pILHead = NULL; LoadInputLanguage();}CInputLanguage::~CInputLanguage(){ Clear();}//消除链表内存。void CInputLanguage::Clear(){ IL* p1,*p2; p1 = m_pILHead; while( p1 ) { p2 = p1; p1 = p1->pNext; delete(p2); } m_pILHead = NULL;}//获得输入法信息链表头结点指针。IL* CInputLanguage::GetInputLanguageList(){ return m_pILHead;} 使用此类时,只要将其头文件包括到要调用的文件中,调用GetInputLanguageList函数可以得到输入法信息链表的头结点指针,通过遍历此链表得到所有已安装的输入法的信息;通过SelectInputLanguage函数可以自由的控制输入法了 谢谢楼上两位其实切换输入法,一般情况下只要模拟shift+ctrl两个键就可以了我的主要问题其实是:如何捕捉状态栏的点击某一ID事件就是单击状态栏中ID_ABCDEFG的消息ON_COMMAND(ID_ABCDEFG,OnChangeIME)//在框架中,我手动这样添加,是没有任务效果的,不知道为什么 聊天软件图文混合问题 recv到网页数据的查找问题 关于ListBox初始化问题!白送分了!!! 请大家推荐几本学习Hook的书,不胜感激! 不用MFC的CEvent,用win32怎样用事件同步线程? 高分求购ADO连接局域网内Access数据库的代码,分不够可以再加!!! 希望和大家交个朋友! 不解的问题 再提一个菜鸟问题:我怎么样删除工程中不需要的文件啊? 在安装钩子时出错,Getlasterror返回:没有模块句柄,无法设置非本机连接 VC 控件Combobox 内存释放问题? 打开连接字串为什么说对象关闭时不能操作
BOOL WINAPI ImeInquire(LPIMEINFO
lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR lpszOption)
{ lpIMEInfo->dwPrivateDataSize = sizeof(CONTEXTPRIV);//系统根据它为INPUTCONTEXT.hPrivate分配空间
lpIMEInfo->fdwProperty = IME_PROP_KBD_CHAR_FIRST |
#ifdef _UNICODE
IME_PROP_UNICODE |
#endif IME_PROP_SPECIAL_UI | IME_PROP_END_UNLOAD ; lpIMEInfo->fdwConversionCaps = IME_CMODE_FULLSHAPE |
IME_CMODE_NATIVE;
lpIMEInfo->fdwSentenceCaps = IME_SMODE_NONE;
lpIMEInfo->fdwUICaps = UI_CAP_2700;
lpIMEInfo->fdwSCSCaps = 0;
lpIMEInfo->fdwSelectCaps = SELECT_CAP_CONVERSION;
_tcscpy(lpszUIClass,CLSNAME_UI);
return TRUE;
}lpIMEInfo->fdwProperty告诉Windows系统您编写的输入法的一些特征,注意一下IME_PROP_END_UNLOAD这个标志,有了它您编写的输入法会随着启动您的输入法的应用程序(如NotePad)的退出而退出,否则它将长驻于系统中,这也是为什么很多输入法在升级安装时需要首先重新启动电脑的原因。在这个接口中还有一点需要特别注意,那就是lpIMEInfo->dwPrivateDataSize,至少我是经过很多次测试才基本证实Windows根据该值为INPUTCONTEXT.hPrivate分配空间。此外如果您修改了这个接口,按照我个人的经验是需要重新调用ImmInstallIME来安装。在安装完成后,在输入法列表中应该已经有了您自己的输入法。点击调试,由于它是一个DLL,您需要先选择一个宿主程序,一般选择“记事本”,以调试方式启动“记事本”后,在这个“记事本”中打开您的输入法,您就可以在源代码中设置断点了。需要说明的是,VC6.0调试DLL不太好用,首先需要打上SP5或者SP6,这样也不能够在DLL启动的时候就设置断点,推荐使用.net来调试。输入法上下文(HIMC):HIMC是什么?在输入法编程时必然要接触到输入法上下文这个术语,刚接触时听起来实在是半懂不懂。由于输入法是一个插件,它需要和调用它的应用程序通讯,在输入法中生成的编码及重码信息保存在哪里应用程序才能正确的读取呢?答案就在于输入法上下文。输入法上下文是由User.exe(一个系统进程)为应用程序分配的内存句柄,在应用程序中启动的输入法在这块内存中写入数据,User.exe再将数据传递到应用程序。UIWnd:在IME中需要导出一个接口,原型如LRESULT WINAPI UIWndProc(HWND hUIWnd, UINT message,WPARAM wParam, LPARAM lParam),hUIWnd是由User.exe传过来的窗口句柄,它是输入法中创建的窗口如编码窗口,重码窗口,状态栏窗口的宿主(Owner),初学输入法编程的人可能会问这个窗口显示在哪里呢?其实它并不是一个普通的窗口,它只是一个用来传递Windows消息的窗口(Message Only),在使用时,您不需要关心它在哪里,只需要使用它就好了。一个IME需要导出19个(Win98版本)接口,但是对于一个只需要实现一般意义的文字输入的软件,您只需要实现几个基本的接口就可以让输入法正常工作了。下面逐一介绍一下这几个接口。
/**********************************************************************//* ImeSelect() *//* Return Value: *//* TRUE - successful, FALSE - failure *//**********************************************************************/BOOL WINAPI ImeSelect(HIMC hIMC,BOOL fSelect)
在这个接口中,系统通知输入法当前是否打开了输入法输入。一般输入法启动时会调用一次,在一些软件(如EmEditor)中提供打开与关闭输入法的功能就是通过这个接口实现的。如果打开输入法,一般会在这个接口中做一些数据的初始化工作。/***********************************************************************/
/*系统调用这个接口来判断IME是否处理当前键盘输入 */
/*HIMC hIMC:输入上下文 */
/*UINT uKey:键值 */
/*LPARAM lKeyData: unknown */
/*CONST LPBYTE lpbKeyState:键盘状态,包含256键的状态 */
/*return : TRUE-IME处理,FALSE-系统处理 */
/*系统则调用ImeToAsciiEx,否则直接将键盘消息发到应用程序 */
/**********************************************************************/
BOOL WINAPI ImeProcessKey(HIMC hIMC,UINT uKey,LPARAM lKeyData,CONST LPBYTE lpbKeyState)观察注释您可以看到在个接口是用来判断用户敲击的哪个键需要处理,哪个键又应该交给系统自己处理,如果输入法需要自己处理用户输入的键,则在这个接口中返回true,否则返回false。/****************************************************************************************************************/
/* function:应用程序调用这个接口来进行输入上下文的转换,输入法程序在这个接口中转换用户的输入 */
/* UINT uVKey:键值,如果在ImeInquire接口中为fdwProperty设置了属性IME_PROP_KBD_CHAR_FIRST,则高字节是输入键值*/
/* UINT uScanCode:按键的扫描码,有时两个键有同样的键值,这时需要使用uScanCode来区分 */
/* CONST LPBYTE lpbKeyState:键盘状态,包含256键的状态 */
/* LPDWORD lpdwTransKey:消息缓冲区,用来保存IME要发给应用程序的消息,第一个双字是缓冲区可以容纳的最大消息条数 */
/* UINT fuState:Active menu flag(come from msdn) */
/* HIMC hIMC:输入上下文 */
/* return : 返回保存在消息缓冲区lpdwTransKey中的消息个数 */
/****************************************************************************************************************/
UINT WINAPI ImeToAsciiEx (UINT uVKey,UINT uScanCode,CONST LPBYTE lpbKeyState,LPDWORD lpdwTransKey,UINT fuState,HIMC hIMC)
这个接口可以说是输入法最重要的部分,程序员需要在这个接口中实现编码与重码的转换,转换完成或者显示在编码窗口及重码窗口,或者发送到应用程序。由于在这个接口中没有传入窗口句柄,如果通知输入法程序的窗口更新显示呢?当然我们可以使用全局变量,在此我个人推荐的方法是使用IME消息(没有什么道理),您将消息类型、参数保存到lpdwTransKey指示的缓冲区中,User.exe会根据消息类型做相应的处理并传递到UIWnd这个窗口中。
那么如何输入文字呢?要输入文字需要3个消息配合使用,分别是WM_IME_STARTCOMPOSITION、WM_IME_COMPOSITION和WM_IME_ENDCOMPOSITION,它们分别指示开始输入编码,输入编码或者结果(视参数而异)及编码输入完成。在开始编写输入法的时候,为了省事,我的输入法在用户确定要输入一个重码时才连续调用这3个消息以向编码器中输入文字。由于WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION需要成对使用,这种方法可以确保它们配对。最初这种方式工作得很好,但是后来发现在一些软件中出现兼容性问题。如“智能五笔”在“遨游”中就存在这个问题,在“遨游”中的地址栏中打开“智能五笔”,当需要使用回退键来删除错误输入的编码时,会发现删除的不是编码窗口中的编码而是编辑器中的文字。这是因为类似“遨游”这类软件主动接管了按键输入如处理一些控制键,当它发现这些控制键不在WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION这两个消息之间时就自己处理控制键而不是先交给User.exe了。因此正确的流程应该是在开始输入编码时发送WM_IME_STARTCOMPOSITION,输入结束后发送WM_IME_ENDCOMPOSITION消息。
/**********************************************************************/
/* UIWndProc() */
/* IME UI window procedure */
/**********************************************************************/
LRESULT WINAPI UIWndProc(HWND hUIWnd, UINT message,WPARAM wParam, LPARAM lParam)
这是一个非常重要的接口,基本上一它负责各种消息的传递。一般您需要在这个接口中根据不同的消息类型,实现输入法窗口(如编码窗口、重码窗口、状态栏窗口)的显示、隐藏及更新等操作。这个接口实现的功能可能非常复杂,视情况而异,在此就不做更加深入的说明了。在使用时可以参见示例工程。BOOL WINAPI ImeConfigure(HKL hKL,HWND hWnd, DWORD dwMode, LPVOID lpData)
这是最后一个需要注意的接口,在显示输入法属性配置时会Windows会调用这个接口。基本的接口就介绍到这里,下面谈一谈我个人在编写输入法程序时遇到的一些问题或者发现的一些需要注意的地方。1、关于输入法窗口:阅读一些输入法的代码会奇怪,为什么输入法窗口在创建时需要指定WM_DISABLE属性呢?原来是因为如果不指定这个属性标志,在打开输入法时,会导致当前的应用程序失去输入焦点。但是指定了这个标志后,输入法窗口不能收到鼠标消息怎么办?解决的方法就在于WM_SETCURSOR这个消息。这个消息不管窗口是否可用,只要有鼠标在窗口内窗口都会收到。您可以在这个消息中模拟鼠标消息也可以选择调用SetCapture这个函数,这样窗口就可以收到鼠标消息了。2、关于窗口模式:使用了几种输入法后,你会发现,有的输入法的编码窗口和重码窗口是一个窗口,有的又是两个窗口,它们有什么区别?或者有的人会觉得这个问题很可笑,但是当您研究了一段输入法可能就会发现您也有类似的问题:因为在输入法的导出接口中关于用户界面的函数就有4个,其它3个分类对应3个窗口回调函数。事实上它们并没有本质的区别,关键在于您的输入法的使用范围。一些软件(如某些游戏)为了界面的整体美观,不希望用户在打开输入法时显示输入法自己的窗口,而是希望输入法按照它的意愿将输入法窗口需要显示的内容显示在它创建的窗口中,英文称之为IME Aware。由于我自己的输入法目标不是在游戏中使用,所在并没有按照这个规矩来管理输入法窗口,而是为了简化,将编码窗口和重码窗口显示的内容放到了一个窗口中。3、关于自定义消息:UIWndProc在WM_IME_NOTIFY中提供了一个IMN_PRIVATE,最初我理解为这个消息应该和WM_USER一样,当我需要不只一人自定义消息时只需要在这个ID的基础上增加值就好了。但事实是您定义的值可能是系统已经占用的(视Windows的版本而异),您能够使用的自定义消息应该只有这一个,为了指示多个消息类型,我使用的方法是在WM_IME_NOTIFY的LPARAM中进行区分。4、调试信息输出:一般编写输入法都不会使用MFC,为了输出调试信息,一般只能使用OutputDebugString这个API,在示例代码中的helper.c中我编写了一个模拟TRACE的函数Helper_Trace,您可以用这个函数来将调试信息输出到调试窗口。5、最后再谈一谈输入法类型:前面提到输入法分为外挂式和IME两种,但是目前一些输入法发展了第3种类型,那就是结合这两种类型的优点。例如拼音加加,启动拼音加加您会发现进程列表里会多一个拼音加加的服务进程,其实它才是拼音加加输入法的内核即数据处理部分。拼音加加的IME部分只是一个外壳,它提供传统的IME输入法一样的系统兼容性。在我的输入法中也采用了这种结构,使用内存文件映射及普通的Windows消息结合来实现两个进程间的通讯。您可以在我的输入法的源代码中找到进程间的通讯源代码及输入法代码。好象没有更多的经验可言,总之,输入法其实并不神秘,在我看来,只要能够在VC中跟踪代码,我就不相信我会搞不定它!一家之言,如果有什么错误,还请大家批评指正。http://www.vckbase.com/document/viewdoc/?id=1807
有源码
不过楼主似乎只要切换输入法就可以了吧,这里有篇文章
在Windows系统中一般都安装了至少三种输入法,在输入数据时常常会切换输入法,虽然Windows系统提供了切换快捷健,但对输入工作还是带来了不少麻烦。如果在应用程序中为用户提供智能输入法自动切换,那么这样的应用程序就显得更加专业、更加具有竞争力。不知你可用过Access,在表数据输入时Access自动切换输入法,如某字段需要输入英文时自动切换到En输入状态,如另一字段需要输入中文自动切换到某中文输入状态。 本文将对如何在Windows应用程序中动态的控制输入法的技术进行探讨。在DELPHI中许多控件都有控制输入法的属性,用户在设计时只要设置好这个属性就可以了,但在VC中并不直接提供对输入法的控制,要在VC应用中实现这种功能必须调用Windows API。在本文中我将用一个类将与输入法操作有关的Windows API函数进行封装,读者可以直接将这个类导入项目工程中,通过操作这个类来实现对输入法的控制,这样更适合于面向对象的开发。 要想控制输入法,首先要解决的问题是如果获得系统已安装的输入法信息。在Windows平台下,每个安装的输入法都在注册表中注册了相关信息。在“HKEY_CURRENT_USER\keyboard layout\preload”键下就可以找到这些信息,键下由以1为基的递增数字做为值名(暂取名为数字号),其值的内容是一个由八个数字组成的字符串(暂取名为代号,如"e0040804"),其中左4位是设备代码(device identifier),右4位是语言代码(language identifier)。例如上面:左e004指智能ABC,右0804指大陆中文。在MSDN中对所有代码做了详细的说明,如感兴趣请浏览MSDN相关内容。另外要说明一点的是在Windows98版本中输入法注册信息与上面说明略有不同,它是将已安装的输入法的数字号做为…\Preload下面的子键,而Windows2000将数字号做为…\Preload键下的值。 通过读取注册表中的输入法信息,可以列出所有已安装的输入法,但得到的输入法信息只是一些让人难懂的数字串,如何将这些数字串翻译成易懂的文字说明呢?同样, 在HKEY_LOCAL_MACHINE:"System\CurrentControlSet\Control\Keyboard Layouts\"键下注册了这些信息,它的子键名为输入法代号(keyboard layout),内容为该输入法的ime文件,名称等信息。到此,我们已经了解了Windows系统控制输入法的原理知识,下面我们开始着手创建一个控制输入法的C++类,主要步骤如下: 1. 创建一个新类,新类名为:CInputLanguage 2. 新建一个保存输入法信息的结构。当加载系统已安装的输入法信息时,用一个此结构的链表来保存输入法信息。struct IL{
char ilID[15]; //输入法代号。
char szName[100];//输入法的说明文字。
IL* pNext;
}; 3. 加入一个私有的成员变量 IL* m_pILHead; 4. 加入加载输入法列表信息的成员函数//此函数只针对Windows2000以上版本,如要在Windows98版本的代码请与笔者联系。BOOL CInputLanguage::LoadInputLanguage()
{
HKEY hKey,hKey1;
DWORD cp=16;
char lp[15];
CString szID;
CString szKeyName,szKeyName1;
szKeyName = "Keyboard Layout\\Preload";
szKeyName1 = "System\\CurrentControlSet\\Control\\Keyboard Layouts\\"; int i=1;
szID.Format("%d",i); DWORD lpT=REG_SZ;
if(::RegOpenKey(HKEY_CURRENT_USER,szKeyName,&hKey)==ERROR_SUCCESS )
{
While( ::RegQueryValueEx(hKey,szID,NULL,&lpT,(LPBYTE)lp,&cp) == ERROR_SUCCESS )
{
CString szTempName;
szTempName = szKeyName1 + (LPCTSTR)(LPTSTR)lp;
if(RegOpenKey(HKEY_LOCAL_MACHINE,szTempName,&hKey1)==ERROR_SUCCESS )
{
char lpD[100];
DWORD lpS=100; //DataSize
if(RegQueryValueEx(hKey1,"Layout text",NULL,&lpT,(LPBYTE)lpD,&lpS)==ERROR_SUCCESS)
{
IL* p1,*p2;
p1 = m_pILHead;
p2 = new(IL);
strcpy(p2->ilID,lp);
strcpy(p2->szName,lpD);
p2->pNext = NULL;
if( p1 )
{
while( p1->pNext ){ p1 = p1->pNext ; }
p1->pNext = p2;
}
else
{
m_pILHead = p2;
}
}
}
::RegCloseKey(hKey1);
i++;
szID.Format("%d",i);
}
} ::RegCloseKey(hKey);
return (m_pILHead != NULL );}
5. 加入选择输入法成员函数BOOL CInputLanguage::SelectInputLanguage(IL *pIL){
if( !pIL ) return FALSE;
HKL hkl;
hkl=LoadKeyboardLayout(pIL->ilID,KLF_ACTIVATE);//装载输入法
if(hkl==NULL) return FALSE;
else{
ActivateKeyboardLayout(hkl,KLF_SETFORPROCESS);//激活输入法
}
return TRUE;}
6. 其它部分CInputLanguage::CInputLanguage(){
m_pILHead = NULL;
LoadInputLanguage();}CInputLanguage::~CInputLanguage(){
Clear();
}//消除链表内存。void CInputLanguage::Clear(){
IL* p1,*p2;
p1 = m_pILHead; while( p1 )
{
p2 = p1;
p1 = p1->pNext;
delete(p2);
}
m_pILHead = NULL;}//获得输入法信息链表头结点指针。IL* CInputLanguage::GetInputLanguageList(){
return m_pILHead;
} 使用此类时,只要将其头文件包括到要调用的文件中,调用GetInputLanguageList函数可以得到输入法信息链表的头结点指针,通过遍历此链表得到所有已安装的输入法的信息;通过SelectInputLanguage函数可以自由的控制输入法了
ON_COMMAND(ID_ABCDEFG,OnChangeIME)//在框架中,我手动这样添加,是没有任务效果的,不知道为什么