输入法编辑器(IME)可以减轻用户在输入他们可能想要的所有字符组合时所需的记忆负担。其工作过程是监视用户的键盘动作,预测用户可能想要输入的字符,然后提供一组可供用户选择输入的字符。
缺省情况下,IME提供一个IME窗口用来显示用户按下的按键以及符合当前按键组合定义的候选字符列表。应用程序可以使用输入法管理器(IMM)函数和消息来创建并管理它们自己的IME窗口,然后就可以提供一个定制的使用IME转换能力的实例了――即创建一个输入法程序。
IMM功能仅在东亚地区(中国、日本和韩国等)发行的Windows操作系统上才提供有。在这些系统上,应用程序可以通过传入SM_DBCSENABLED参数的GetSystemMetrics函数来确定IMM是否可用。
对于Windows 2000及其后继版本的系统来说,不管是在哪个语言地区发行的版本都提供了完整的IME支持。不过对于IMM功能来说,仍然只有在安装了东亚语言包的情况下才能提供。一个打开了IME支持的程序可以通过传入SM_IMMENABLED参数的GetSystemMetrics函数来判断当前系统是否支持IMM功能。
有关输入法编辑器的大概情况将分为以下几节来讨论:
· 状态、按键组合和候选窗口
·IME窗口类
·IME消息
·输入环境
·组合字符串
·候选列表
·热键
·用十六进制Unicode编码(点)来输入的IME
我原来也翻译过,为了避免重复劳动,我就把它帖在这里吧。如果有错,请大家指正。
Windows 95 输入法编辑器
翻译:戴石麟译自微软的MSDN DDK
关于Windows 95的多语言IME(输入法编辑器)
在Windows 95中,IME以动态连接库(DLL)的形式提供,与Windows 3.1东方版的IME不同,每个IME作为一个多语言键盘布局来运行。与Windows 3.1 IME相比,Windows 95多语言IME具有下述优势:
IME的结构
Windows 95 IME必须提供两个组件:IME转换接口和IME用户界面。IME转换接口以一套从IME模块输出的函数提供。这些函数由IMM调用。IME用户界面以一套窗口提供。这些窗口接收消息并提供IME的用户界面。
IME敏感的应用程序
有以下类型应用程序:
ME用户界面
IME用户界面包括IME窗口、用户界面(UI)窗口和UI窗口的组件。
特点
IME类是预定义的全局窗口类,它实现IME的用户界面部分。IME类有许多与预定义的公共控件窗口相同的特征。IME窗口实例可用CreateWindowEx函数来创建。象静态控件一样,IME类窗口自身不对用户 输入作出反应;代之以接收各类控件消息来实现整个IME用户界面。应用程序可以用IME类创建自己的IME窗口,也可以用ImmGetDefaultIMEWnd函数来获得默认的IME窗口。创建自己的IME窗口或使用默认的IME窗口的应用程序称为IME敏感的应用程序,可以获得下述益处(与Windows 3.1中类似应用程序相比):
默认与应用程序IME窗口
线程初始化时,系统创建默认IME窗口;也就是说,默认IME窗口自动赋予线程。默认IME窗口为IME不敏感的应用程序提供IME用户界面。当IME或IMM生成一条IME消息(WM_IME_*)时,对IME不敏感的应用程序传递一条消息到DefWindowProc函数。DefWindowProc将所需的消息送到默认IME窗口以为应用程序提供默认IME用户界面。IME敏感的应用程序在不从IME挂接消息时也使用默认IME窗口。应用程序仅在需要的时候使用自己的应用程序IME窗口。
IME类
IME类是Windows 95-FE格式的预定义窗口类,就象Edit是预定义类一样。预定义IME类处理IME的整个用户界面,处理来自IME和应用程序(包含IMM函数)的所有控制消息。应用程序用IME类创建自己的IME用户界面。系统IME类不会被任何IME替换。 与IME类相联系的窗口类处理WM_IME_SELECT消息。这条消息包括新选择的IME的键盘布局。IME类使用键盘布局来获取由每个IME定义的类名。IME类使用类名为当前活动IME创建用户界面窗口。
来自IME的UI类
每个IME都必须向系统注册自己的用户界面(UI)类。UI类提供特定的IME功能。IME在附着进程时注册自己的类;也就是说,用DLL_PROCESS_ATTACH调用DllEntry函数时。IME在调用ImeInquire函数时必须指明UI类名。UI类应当以CS_IME风格注册,以便每个应用程序都能使用该UI类。UI类名(包括空结束符)可达16个TCHAR字符。此限制可能会在Windows的未来版本中得到扩展。 当注册用户界面类时,你应当指明八个字节的额外窗口数据(即,将WNDCLASSEX结构的cbWndExtra成员设置为2*sizeof(LONG)。这额外的窗口数据由系统使用。 IME在为应用程序完成任务时可注册任何类和创建任何窗口。
以下例子显示如何注册IME窗口类:
BOOL WINAPI DLLEntry ( HINSTANCE hInstDLL, DWORD dwFunction, LPVOID lpNot)
{
switch (dwFunction)
{ case DLL_PROCESS_ATTACH: hInst= hInstDLL; wc.style = CS_MYCLASSFLAG | CS_IME; wc.lpfnWndProc = MyUIServerWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 2 * sizeof(LONG); wc.hInstance = hInst; wc.hCursor = LoadCursor( NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = (LPSTR) NULL; wc.lpszClassName = (LPSTR) szUIClassName; wc.hbrBackground = NULL; if(!RegisterClass((LPWNDCLASS)&wc)) return FALSE; wc.style = CS_MYCLASSFLAG | CS_IME; wc.lpfnWndProc = MyCompStringWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = cbMyWndExtra; wc.hInstance = hInst; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = (LPSTR) NULL; wc.lpszClassName = (LPSTR) szUICompStringClassName; wc.hbrBackground = NULL; if(!RegisterClass((LPWNDCLASS)&wc)) return FALSE; break; case DLL_PROCESS_DETACH: UnregisterClass(szUIClassName,hInst); UnregisterClass(szUICompStringClassName,hInst); break;
}
return TRUE;
}
UI窗口
IME类的IME窗口由应用程序或由系统创建。当IME窗口创建时,由IME本身提供的UI窗口被IME窗口创建并拥有。每个UI窗口都有一个当前 输入场景。你可以在UI窗口收到一条IME消息(WM_IME_*)时,通过调用GetWindowLong函数并指明IMMGWL_IMC索引值来获取 输入场景。UI窗口可以参照此 输入场景并处理消息。UI窗口可以在除对WM_CREATE消息作出反应时以外的任何时候获取 输入场景。 IME决不能更改UI窗口的额外窗口数据。如果你的窗口实例需要额外窗口数据,你可以用SetWindowLong和GetWindowLong函数带IMMGWL_PRIVATE参数。IMMGWL_PRIVATE参数提供对UI窗口实例额外数据的一个LONG值的访问,你可将一内存块的句柄存入IMMGWL_PRIVATE区域。 UI窗口过程可以使用DefWindowProc函数,但是UI窗口不能将IME消息传递给DefWindowProc。即使一条IME消息未被UI窗口过程处理,UI窗口也不能将它传递给DefWindowProc。
LRESULT UIWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){ HIMC hIMC; HGLOBAL hMyExtra; switch(msg){ case WM_CREATE: // Allocate the memory bloack for the window instance. hMyExtra = GlobalAlloc(GHND,size_of_MyExtra); if (!hMyExtra) MyError(); // Set the memory handle into IMMGWL_PRIVATE SetWindowLong(hWnd, IMMGWL_PRIVATE, (LONG)hMyExtra); . . . break; case WM_IME_xxxx: // Get IMC; hIMC = GetWindowLong(hWnd,IMMGWL_IMC); // Get the memory handle for the window instance. hMyExtra = GetWindowLong(hWnd, IMMGWL_PRIVATE); lpMyExtra = GlobalLock(hMyExtra); . . . GlobalUnlock(hMyExtra); break; . . . case WM_DESTROY: // Get the memory handle for the window instance. hMyExtra = GetWindowLong(hWnd, IMMGWL_PRIVATE); // Free the memory block for the window instance. GlobalFree(hMyExtra); break; default: return DefWindowProc(hWnd, msg, wParam, lParam); }}
UI窗口在当前选定的 输入场景中完成动作。当窗口激活时,UI窗口收到一条提供当前 输入场景的消息。在此之后,UI窗口在当前选定的 输入场景中运行。 输入场景必须提供UI窗口显示工作窗口、状态窗口等所需的所有信息。 UI窗口参照 输入场景,但它不必自己刷新该场景。当UI窗口需要刷新 输入场景时,它应当调用IMM函数,因为 输入场景是由IMM管理的。当 输入场景改变时,IMM和IME都收到通知。 例如,有时当鼠标点击时,UI窗口需要改变 输入场景的转换模式。要设置转换模式,UI窗口调用ImmSetConversionMode函数来为NotifyIME生成一条通知并将WM_IME_NOTIFY消息送至UI窗口。如果UI窗口要改变转换模式的显示,UI窗口应当等待WM_IME_NOTIFY消息。
UI窗口的组件
UI窗口可以参照当前 输入场景来注册和显示工作窗口和状态窗口。UI窗口组件的类风格必须包含CS_IME。UI窗口实例从当前 输入场景接收诸如工作字串、字型、位置之类的信息。上应用程序的一个窗口获得焦点时,系统收到为窗口所拥有的 输入场景并将当前 输入场景设置给UI窗口。系统将带有 输入场景句柄的WM_IME_SETCONTEXT消息送到应用程序,应用程序将此消息传递到UI窗口。如果当前 输入场景被替换,UI窗口应当重画工作窗口。每当当前场景变化,UI窗口都会显示正确的工作窗口,确保IME的状态。 UI窗口可以创建自己的字窗口或弹出窗口来显示其状态、工作字串或候选名单。这些窗口必须为UI窗口所拥有,并创建为失效窗口。IME创建的任何窗口都不应当获得焦点。 输入场景
默认输入场景
系统通过默认给每个线程一个 输入场景。该场景为该线程所有对IME不敏感的窗口共享。
将输入场景与窗口相关联
应用程序的窗口将其窗口句柄与 输入场景相关联来维持IME状态,包括当前工作字串。一旦应用程序将 输入场景与窗口句柄相关联,系统在窗口激活时将自动选择该场景。利用此特征,应用程序将不需象在Windows 3.1下那样进行复杂的获得、放弃焦点的处理。
使用输入场景
当应用程序或系统创建新的 输入场景时,系统准备新的 输入场景。新的 输入场景已经包括IMCC,即由hCompStr、hCandInfo、hGuideLine、hPrivate和hMsgBuf组成的IMC的组件。IME基本上不需创建 输入场景和 输入场景的组件。IME可以通过锁定它们来改变其大小并能获得指向组件的指针。
访问HIMC
要访问 输入场景,IME必须调用ImmLockIMC来获得指向 输入场景的指针。ImmLockIMC递增IMC的IMM锁定记数,而ImmUnlockIMC则递减它。
访问HIMCC
要访问 输入场景的一个组件,IME必须调用ImmLockIMCC来获得指向IMCC的指针。ImmLockIMCC递增IMCC的IMM锁定记数,而ImmUnlockIMC则递减它。ImmReSizeIMCC可以将IMCC调整至特定的大小。 有时,IME可能需要自己创建 输入场景的一个组件。在这种情况下,IME可以调用ImmCreateIMCC函数并获得IMCC的句柄。该IMCC可以是INPUTCONTEXT结构的成员(hCompStr、hCandInfo、hGuideLine、hPrivate或hMsgBuf)。 ImmDestroyIMCC销毁 输入场景的一个组件。
怎样使用输入场景
以下例子显示如何使用 输入场景: LPINPUTCONTEXT lpIMC;LPCOMOSITIONSTRING lpCompStr;HIMCC hMyCompStr;if (hIMC) { // It is not NULL context. lpIMC = ImmLockIMC(hIMC); if (!lpIMC) { MyError( "Can not lock hIMC"); return FALSE; } // Use lpIMC->hCompStr. lpCompStr = (LPCOMPOSITIONSTRING) ImmLockIMCC(lpIMC->hCompStr); // Access lpCompStr. ImmUnlockIMCC(lpIMC->hCompStr); // ReSize lpIMC->hCompStr. if (!(hMyCompStr = ImmReSizeIMCC(lpIMC->hCompStr,dwNewSize)) { MyError("Can not resize hCompStr"); ImmUnlockIMC(hIMC); return FALSE; } lpIMC->hCompStr = hMyCompStr; ImmUnlockIMC(hIMC);}
生成消息
IME需要生成IME消息。当IME开始一次回话时,IME必须生成WM_IME_STARTCOMPOSITION消息。如果IME改变工作字符串,IME必须生成WM_IME_COMPOSITION消息。从IME来的事件是通过向与 输入场景相关联的窗口生成消息来实现的。IME基本上是利用ImeToAsciiEx函数的参数所提供的lpdwTransKey缓冲区来生成消息的。当ImeToAsciiEx被调用时,IME将消息放进lpdwTransKey缓冲区。即使ImeToAsciiEx未被调用,IME还是能够利用 输入场景的消息缓冲区向与 输入场景相关联的窗口生成消息。 输入场景以内存块句柄的形式拥有消息缓冲区。IME将消息放进由消息缓冲区句柄提供的内存块中。然后,IME调用ImmGenerateMessage。ImmGenerateMessage函数将存储于消息缓冲区里的消息发送到适当的窗口。
状态、按键组合和候选窗口构成了IME程序的用户界面。状态窗口的出现就表示IME程序已经打开,并向用户提供各种转换方式的提示。每当用户输入文本时,按键组合窗口就会出现,并根据采用的转换方式显示用户输入的文本或按键组合对应的转换文本。候选窗口是和按键组合窗口一起出现的,用以列出所有候选输入文本或按键组合窗口中的键盘字符。用户可以通过翻动候选内容来查找选择他们希望输入的文本,当选择输入完成之后,IME程序控制焦点再次返回到按键组合窗口上。当用户录入完需要使用输入法来录入的文本后,就可以把IME程序的窗口关掉了。
IME程序向应用程序发送用户选定的输入文本的工作是通过WM_IME_CHAR或WM_IME_COMPOSITION/GCS_RESULT消息来完成的。如果客户端程序没有处理这些消息,则系统会用DefWindowProc函数来将它们转换为一个或多个WM_CHAR消息。
缺省情况下,复杂文本输入所需的状态、按键组合和候选窗口的创建和管理工作是由操作系统负责完成的。对于大多数应用程序来说,采用这种默认的IME处理方式就足以满足要求了。这种程序的IME支持完全依赖于操作系统,因为它们不了解操作系统执行IME窗口管理的大多数工作过程,所以被称为“IME无知”的程序。
对应的,能够参与到IME窗口的创建和管理工作中的程序则被称之为“IME敏感”的程序。这类程序可能需要控制缺省IME窗口的操作、位置和外观,这就需要向这些窗口发送消息或拦截并处理从这些窗口发出的消息。在某些情况下,应用程序还可能创建它们自己的IME窗口并为它们自己定制的状态、按键组合和候选窗口提供完整的处理过程。
IME窗口类是一个预设的系统全局类,它定义了标准IME窗口的外观和行为,有点类似于应用程序使用CreateWindowEx函数创建的公共控件窗口类。跟静态窗口一样,IME窗口不能自己响应用户的输入动作。当用户输入时,系统会向IME发送特定的消息,然后由IME或应用程序处理这些控件消息来实现对用户行为的响应。
有时,IME感知的程序会自己使用IME类来创建定制的IME窗口。相较于缺省的IME窗口处理过程,使用定制窗口的好处是可以控制窗口配置。
当某些影响IME窗口的事件发生时,操作系统会向应用程序的窗口处理过程发送IME窗口消息。比如,当窗口激活时系统会给应用程序发送WM_IME_SETCONTEXT消息。IME无知的程序会忽略此消息,交由DefWindowProc函数来处理――发送给相应的缺省IME窗口(即当前程序激活的那个IME窗口)。而IME感知的程序则会自己处理这些消息或把这些消息传递给自己的IME窗口。
应用程序可以以命令的形式直接实现对IME窗口的控制,比如使用WM_IME_CONTROL消息来改变按键组合窗口的位置。IME使用通知类消息WM_IME_COMPOSITION来告知应用程序按键组合字符串已改变,而通常情况下IME窗口状态的改变则是由WM_IME_NOTIFY消息来通知的。
所谓“输入环境”其实是系统内由IME管理的一个数据结构,其中包含了供IME窗口使用的IME状态信息。缺省情况下,操作系统会为每一个线程创建并分配一个输入环境。在线程中,缺省的输入环境是一个共享资源并关联到每一个新建的窗口。
如果想在应用程序中获取或设置IME信息的话,必须先取得与一个特定窗口相关联的输入环境的句柄。可以通过ImmGetContext函数来得到这个句柄,然后才能通过它调用IMM函数来获取和设置IME属性(比如按键组合窗口的样式、组合形式以及状态窗口的位置等)。如果应用程序不再需要使用输入环境,就应该及时使用ImmReleaseContext函数释放它。
因为缺省的输入环境是一个共享资源,应用程序对它作出的任何更改都会影响到线程中所有的窗口。不过,应用程序可以通过创建自己的输入环境并关联到线程中一个或多个特定的窗口上去,从而避免可能会影响到不相干窗口的输入环境的情况。应用程序对特定输入环境的更改只作用于与该环境相关联的那些窗口。
提供输入环境创建功能的函数是ImmCreateContext,把环境分配给一个窗口的工作则需要通过ImmAssociateContext函数来完成,该函数结束时会返回原来与窗口关联的输入环境的句柄,如果应用程序之前没有给这个窗口专门分配一个输入环境,那么函数返回的就是缺省输入环境的句柄。对这个"原先输入环境"返回值的典型利用实例是,应用程序在给窗口分配特定输入环境的同时保存原来的环境句柄,当特定的输入环境不再需要使用的时候再把原先的环境重新分配给窗口。
一旦把一个输入环境分配给了某个窗口,操作系统就会在窗口被激活和获得输入焦点的时候自动选用分配给这个窗口的输入环境。按照IME的运行机制,从此该输入环境中的样式和其他信息都会作用于这个窗口上所发生的键盘输入动作。
应用程序在结束运行之前,必须销毁所有它自己定制的输入环境:首先通过ImmAssociateContext函数把线程中所有窗口与专门的输入环境所建立的关联去除掉,然后调用ImmDestroyContext函数把撤销了一切关联的输入环境销毁。
组合字符串是指当前显示在按键组合窗口中的文本,这个文本就是IME最终要用来转换成候选字符的文本。组合字符串可以由一个或多个"子串"组成,子串是可以给IME用来转换成一个候选字符的最小输入字符组合。应用程序可以通过 ImmGetCompositionString 和 ImmSetCompositionString 函数来获取或设置组合字符串。
当用户在按键组合窗口中 输入文本的时候,IME就会跟踪组合字符串的状态,包括:属性信息、子串信息、按键信息和 输入光标位置等。应用程序可以通过 ImmGetCompositionString函数来实时地获得这些 输入字符组合过程状态。
属性信息是一个字节数组,用来表示组合字符串中的字符的状态。一个子串中的所有字符必须具有相同的属性。数组的每个字节单元对应于组合字符串中的每个字节,包括组合字符串中可能出现的每个双字节字符上的引导字节和尾随字节。对于属性数组中每个字节单元来说,其取值的第0到3位可以是下列常量中的任一组合。
除了这四位之外其余二进制位保留。在日文输入法中,任何未被转换的平假名、片假名或字母数字字符都会被赋予ATTR_INPUT属性;朝鲜文输入法相应赋予这个属性的IME未转换字符是谚文字母;而各种繁体中文和简体中文输入法都设置有它们自己的编码字符集。
组合字符串状态中的子串信息是一个双字(32位)单元数组,用来表示每个子串所在的位置。数组开始部分的每个单元对应一个子串,最后一个单元的值用来表示整个组合字符串的长度。子串所在位置以其开始的字符在组合字符串中的位置来表示,字符在组合串中的位置值从0开始计算。假设一个组合字符串中有两个子串的话,那么子串信息数组中就应该有三个单元:第一个单元值为0;第二个单元的值表示第二个子串在组合串中的偏移;第三个单元值是组合串的长度。注意以上所说的子串位置和组合字符串长度的值均以字节为单位――也就是说假设应用程序是以ANSI的方式处理文本的。对于以Unicode的方式处理文本的程序来说,因为Windows采用UTF-16编码格式作为Unicode内码实现方案,所以计算子串位置和组合字符串长度的值以UTF-16编码单元(即很多提供Unicode字符/串数据类型的编程语言里面所说的“宽字符”)为单位。
组合字符串状态中的按键信息就是由用户按下的键盘字符组成的一个空结束字符串。
组合字符串状态中的输入光标位置指的是用户在按键组合窗口中输入组合字符串时,那个用来编辑输入字符的光标所在的位置。它表示光标在当前组合字符串中的位置,从0开始计算。如果这个位置值为0,则表示光标位于当前组合字符串的第一个字符前面;如果这个位置值等于组合串的长度,就表示光标位于组合串最后一个字符的后面;如果这个位置值为1,则光标不出现。对于ANSI程序,以上提及的位置和长度值以字节为单位;而对于Unicode程序,则以Unicode字符为单位。
应用程序可以通过ImmSetCompositionString函数来设置组合字符串或其状态,需要注意的是当做出这些更改之后必须相应更新按键组合窗口的显示――这个函数允许程序向窗口发送一则通知类消息。通常,应用程序在对组合字符串状态作出多项改变的过程中不会逐项向窗口报告,而是在设置最后一项状态的函数调用中才向按键组合窗口发送一则“总的”通知消息。
在使用标准编辑控件的时候,有两个消息可以用来改变IME对组合字符串状态更新的处理过程--它们是EM_GETIMESTATUS和EM_SETIMESTATUS。更详细的内容请查阅标准编辑控件主题。
在程序中,候选列表被表现为一个 CANDIDATELIST结构,该结构包含了一个字符串数组,这些字符串就是提供给用户选择 输入的候选文字内容。应用程序可以通过 ImmGetCandidateListCount和 ImmGetCandidateList函数获得候选列表中的信息。
用户通过热键可以快速地更换 输入法的 输入模式或切换到另一个 输入法。虽然操作系统没有提供给应用程序添加 输入法热键的功能,但我们可以用 ImmSimulateHotKey函数来启用自己的热键响应例程。
在下一节 HexToUnicode输入法当中,我们将介绍一个在十六进制编码点和Unicode字符之间进行转换的 输入法。
多格式编辑控件从3.0版开始内置了一种HexToUnicode 输入法,它允许用户通过两种热键来执行十六进制编码点和Unicode字符之间的转换。
第一种热键转换方法是:用户 输入字符的十六进制编码点,然后按下组合键ALT+X把 输入光标位置上的编码数字串替换成相应的Unicode字符。如果 输入光标当前位置上设置的字体不支持转换出来的字符,控件会自动选择系统中一个合适的字体来显示它。另外,如果当前 输入光标位置上的不是一个有效的十六进制编码点数字串的话,则按下ALT+X组合键就是把这个位置上的那个字符替换成它的十六进制Unicode编码点数字串。这个方法的另一个作用就是可以用来确定当前系统中是否缺少可以显示某个字符的字体资源。如果一个数字串在转换成字符时有可能造成歧义的话,用户可以先选中要转换的那些数字符后再进行转换,以确保得到想要的转换结果。使用ALT+X热键时需要注意一个问题就是很多软件会把它作为执行"退出"的键盘快捷方式。像Office则不提供退出的键盘快捷方式,而只是在文件菜单上提供了一个"退出"菜单项。
第二种热键转换方法是使用数字小键盘:按下ALT键不放,然后在数字小键盘上 输入字符的十进制编码点(大于255)。相对于第一种方法,这个方法不太好用,因为在整个编码点 输入过程中用户都没有办法确认自己 输入的是否正确并修正。
关于如何使用IME的问题将分为如下几节进行讨论:
· WM_IME_COMPOSITION消息的处理
· IME和Unicode
· IME自动更正功能的使用
· IME感知的多线程应用程序的开发
应用程序通过处理 WM_IME_COMPOSITION消息对 lParam参数的位进行测试并调用 ImmGetCompositionString函数来获得组合字符串的信息或数据。下面这段示例代码演示了如何编程实现上述处理过程,注意在从IME处获取组合字符串前必须为字符串分配适当的内存:
[Copy to clipboard]
CODE:
HIMC hIMC;
HWND hWnd;
DWORD dwSize;
HGLOBAL hstr;
LPSTR lpstr;
case WM_IME_COMPOSITION:
if (lParam & GCS_RESULTSTR)
{
hIMC = ImmGetContext(hWnd);
If (!hIMC)
MyError(ERROR_NULLCONTEXT);
// Get the size of the result string.
dwSize = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);
// increase buffer size for terminating null character,
// maybe it is in UNICODE
dwSize += sizeof(WCHAR);
hstr = GlobalAlloc(GHND,dwSize);
if (hstr == NULL)
MyError(ERROR_GLOBALALLOC);
lpstr = GlobalLock(hstr);
if (lpstr == NULL)
MyError(ERROR_GLOBALLOCK);
// Get the result strings that is generated by IME into lpstr.
ImmGetCompositionString(hIMC, GCS_RESULTSTR, lpstr, dwSize);
ImmReleaseContext(hWnd, hIMC);
// add this string into text buffer of application
GlobalUnlock(hstr);
GlobalFree(hstr);
}
Windows 98/Me、Windows NT/2000/XP:在原先支持ANSI界面的基础上,Windows增加了IME对Unicode界面的支持。Windows 98/Me支持除了 ImmIsUIMessage之外所有的Unicode函数,同时在Windows 98/Me系统中所有的消息都是基于ANSI的。因为Windows 98/Me不支持Unicode消息,应用程序可以使用 ImmGetCompositionString函数从Windows 98/Me上基于Unicode的IME处获取Unicode字符。
IME及其对于Unicode的处理有两个需要注意的事项。其一是Unicode版本的IME函数在计算缓冲区所需的字节数时是以16位Unicode字符为考虑的;其二是IME通常从 WM_CHAR和 WM_IME_CHAR消息中接收到的是Unicode字符而不是DBCS字符。
Windows NT/2000/XP:应用程序可以使用 RegisterClassW创建窗口,让它从WM_CHAR和WM_IME_CHAR消息的 wParam参数处获得Unicode(不是DBCS)字符。该功能不能用在Windows 95/98/Me上。
缺省情况下,IME提供一个IME窗口用来显示用户按下的按键以及符合当前按键组合定义的候选字符列表。应用程序可以使用输入法管理器(IMM)函数和消息来创建并管理它们自己的IME窗口,然后就可以提供一个定制的使用IME转换能力的实例了――即创建一个输入法程序。
IMM功能仅在东亚地区(中国、日本和韩国等)发行的Windows操作系统上才提供有。在这些系统上,应用程序可以通过传入SM_DBCSENABLED参数的GetSystemMetrics函数来确定IMM是否可用。
对于Windows 2000及其后继版本的系统来说,不管是在哪个语言地区发行的版本都提供了完整的IME支持。不过对于IMM功能来说,仍然只有在安装了东亚语言包的情况下才能提供。一个打开了IME支持的程序可以通过传入SM_IMMENABLED参数的GetSystemMetrics函数来判断当前系统是否支持IMM功能。
有关输入法编辑器的大概情况将分为以下几节来讨论:
· 状态、按键组合和候选窗口
·IME窗口类
·IME消息
·输入环境
·组合字符串
·候选列表
·热键
·用十六进制Unicode编码(点)来输入的IME
我原来也翻译过,为了避免重复劳动,我就把它帖在这里吧。如果有错,请大家指正。
Windows 95 输入法编辑器
翻译:戴石麟译自微软的MSDN DDK
关于Windows 95的多语言IME(输入法编辑器)
在Windows 95中,IME以动态连接库(DLL)的形式提供,与Windows 3.1东方版的IME不同,每个IME作为一个多语言键盘布局来运行。与Windows 3.1 IME相比,Windows 95多语言IME具有下述优势:
- 作为多语言环境的一个组件来运行
- 为每个应用程序任务提供多个输入场景
- 为每个应用程序线程提供一个活动IME
- 通过应用程序的消息循环(消息顺序不变)为其提供信息
- 为对IME不敏感和对IME敏感的应用程序均提供强大的支持
IME的结构
Windows 95 IME必须提供两个组件:IME转换接口和IME用户界面。IME转换接口以一套从IME模块输出的函数提供。这些函数由IMM调用。IME用户界面以一套窗口提供。这些窗口接收消息并提供IME的用户界面。
IME敏感的应用程序
有以下类型应用程序:
- IME不敏感的应用程序:这种类型的应用程序绝不打算控制IME。但是,如果应用程序接受DBCS字符,用户可以用IME向应用程序输入任何DBCS字符。
- IME半敏感的应用程序:这种类型的应用程序通常控制IME的各种场景,如打开和关闭、工作窗口等,但是它不为IME显示任何用户界面。
- IME全敏感的应用程序:这种类型的应用程序通常显示IME给它的任何信息。
ME用户界面
IME用户界面包括IME窗口、用户界面(UI)窗口和UI窗口的组件。
特点
IME类是预定义的全局窗口类,它实现IME的用户界面部分。IME类有许多与预定义的公共控件窗口相同的特征。IME窗口实例可用CreateWindowEx函数来创建。象静态控件一样,IME类窗口自身不对用户 输入作出反应;代之以接收各类控件消息来实现整个IME用户界面。应用程序可以用IME类创建自己的IME窗口,也可以用ImmGetDefaultIMEWnd函数来获得默认的IME窗口。创建自己的IME窗口或使用默认的IME窗口的应用程序称为IME敏感的应用程序,可以获得下述益处(与Windows 3.1中类似应用程序相比):
- 包含候选列表窗口,每个应用程序可有自己的用户界面窗口实例,所以用户可以在任何操作的中途停下来,将输入焦点改变到另一应用程序。在Windows 3.1日文版中,在改变到另一应用程序时用户必须放弃操作。
- 因为IME用户界面窗口拥有应用程序的窗口句柄,因此它可以为应用程序提供默认的行为。例如,它可以随应用程序移动而自动移动,自动跟踪窗口的光标位置,指出每一应用程序的状态等。
默认与应用程序IME窗口
线程初始化时,系统创建默认IME窗口;也就是说,默认IME窗口自动赋予线程。默认IME窗口为IME不敏感的应用程序提供IME用户界面。当IME或IMM生成一条IME消息(WM_IME_*)时,对IME不敏感的应用程序传递一条消息到DefWindowProc函数。DefWindowProc将所需的消息送到默认IME窗口以为应用程序提供默认IME用户界面。IME敏感的应用程序在不从IME挂接消息时也使用默认IME窗口。应用程序仅在需要的时候使用自己的应用程序IME窗口。
IME类
IME类是Windows 95-FE格式的预定义窗口类,就象Edit是预定义类一样。预定义IME类处理IME的整个用户界面,处理来自IME和应用程序(包含IMM函数)的所有控制消息。应用程序用IME类创建自己的IME用户界面。系统IME类不会被任何IME替换。 与IME类相联系的窗口类处理WM_IME_SELECT消息。这条消息包括新选择的IME的键盘布局。IME类使用键盘布局来获取由每个IME定义的类名。IME类使用类名为当前活动IME创建用户界面窗口。
来自IME的UI类
每个IME都必须向系统注册自己的用户界面(UI)类。UI类提供特定的IME功能。IME在附着进程时注册自己的类;也就是说,用DLL_PROCESS_ATTACH调用DllEntry函数时。IME在调用ImeInquire函数时必须指明UI类名。UI类应当以CS_IME风格注册,以便每个应用程序都能使用该UI类。UI类名(包括空结束符)可达16个TCHAR字符。此限制可能会在Windows的未来版本中得到扩展。 当注册用户界面类时,你应当指明八个字节的额外窗口数据(即,将WNDCLASSEX结构的cbWndExtra成员设置为2*sizeof(LONG)。这额外的窗口数据由系统使用。 IME在为应用程序完成任务时可注册任何类和创建任何窗口。
以下例子显示如何注册IME窗口类:
BOOL WINAPI DLLEntry ( HINSTANCE hInstDLL, DWORD dwFunction, LPVOID lpNot)
{
switch (dwFunction)
{ case DLL_PROCESS_ATTACH: hInst= hInstDLL; wc.style = CS_MYCLASSFLAG | CS_IME; wc.lpfnWndProc = MyUIServerWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 2 * sizeof(LONG); wc.hInstance = hInst; wc.hCursor = LoadCursor( NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = (LPSTR) NULL; wc.lpszClassName = (LPSTR) szUIClassName; wc.hbrBackground = NULL; if(!RegisterClass((LPWNDCLASS)&wc)) return FALSE; wc.style = CS_MYCLASSFLAG | CS_IME; wc.lpfnWndProc = MyCompStringWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = cbMyWndExtra; wc.hInstance = hInst; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = (LPSTR) NULL; wc.lpszClassName = (LPSTR) szUICompStringClassName; wc.hbrBackground = NULL; if(!RegisterClass((LPWNDCLASS)&wc)) return FALSE; break; case DLL_PROCESS_DETACH: UnregisterClass(szUIClassName,hInst); UnregisterClass(szUICompStringClassName,hInst); break;
}
return TRUE;
}
UI窗口
IME类的IME窗口由应用程序或由系统创建。当IME窗口创建时,由IME本身提供的UI窗口被IME窗口创建并拥有。每个UI窗口都有一个当前 输入场景。你可以在UI窗口收到一条IME消息(WM_IME_*)时,通过调用GetWindowLong函数并指明IMMGWL_IMC索引值来获取 输入场景。UI窗口可以参照此 输入场景并处理消息。UI窗口可以在除对WM_CREATE消息作出反应时以外的任何时候获取 输入场景。 IME决不能更改UI窗口的额外窗口数据。如果你的窗口实例需要额外窗口数据,你可以用SetWindowLong和GetWindowLong函数带IMMGWL_PRIVATE参数。IMMGWL_PRIVATE参数提供对UI窗口实例额外数据的一个LONG值的访问,你可将一内存块的句柄存入IMMGWL_PRIVATE区域。 UI窗口过程可以使用DefWindowProc函数,但是UI窗口不能将IME消息传递给DefWindowProc。即使一条IME消息未被UI窗口过程处理,UI窗口也不能将它传递给DefWindowProc。
LRESULT UIWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){ HIMC hIMC; HGLOBAL hMyExtra; switch(msg){ case WM_CREATE: // Allocate the memory bloack for the window instance. hMyExtra = GlobalAlloc(GHND,size_of_MyExtra); if (!hMyExtra) MyError(); // Set the memory handle into IMMGWL_PRIVATE SetWindowLong(hWnd, IMMGWL_PRIVATE, (LONG)hMyExtra); . . . break; case WM_IME_xxxx: // Get IMC; hIMC = GetWindowLong(hWnd,IMMGWL_IMC); // Get the memory handle for the window instance. hMyExtra = GetWindowLong(hWnd, IMMGWL_PRIVATE); lpMyExtra = GlobalLock(hMyExtra); . . . GlobalUnlock(hMyExtra); break; . . . case WM_DESTROY: // Get the memory handle for the window instance. hMyExtra = GetWindowLong(hWnd, IMMGWL_PRIVATE); // Free the memory block for the window instance. GlobalFree(hMyExtra); break; default: return DefWindowProc(hWnd, msg, wParam, lParam); }}
UI窗口在当前选定的 输入场景中完成动作。当窗口激活时,UI窗口收到一条提供当前 输入场景的消息。在此之后,UI窗口在当前选定的 输入场景中运行。 输入场景必须提供UI窗口显示工作窗口、状态窗口等所需的所有信息。 UI窗口参照 输入场景,但它不必自己刷新该场景。当UI窗口需要刷新 输入场景时,它应当调用IMM函数,因为 输入场景是由IMM管理的。当 输入场景改变时,IMM和IME都收到通知。 例如,有时当鼠标点击时,UI窗口需要改变 输入场景的转换模式。要设置转换模式,UI窗口调用ImmSetConversionMode函数来为NotifyIME生成一条通知并将WM_IME_NOTIFY消息送至UI窗口。如果UI窗口要改变转换模式的显示,UI窗口应当等待WM_IME_NOTIFY消息。
UI窗口的组件
UI窗口可以参照当前 输入场景来注册和显示工作窗口和状态窗口。UI窗口组件的类风格必须包含CS_IME。UI窗口实例从当前 输入场景接收诸如工作字串、字型、位置之类的信息。上应用程序的一个窗口获得焦点时,系统收到为窗口所拥有的 输入场景并将当前 输入场景设置给UI窗口。系统将带有 输入场景句柄的WM_IME_SETCONTEXT消息送到应用程序,应用程序将此消息传递到UI窗口。如果当前 输入场景被替换,UI窗口应当重画工作窗口。每当当前场景变化,UI窗口都会显示正确的工作窗口,确保IME的状态。 UI窗口可以创建自己的字窗口或弹出窗口来显示其状态、工作字串或候选名单。这些窗口必须为UI窗口所拥有,并创建为失效窗口。IME创建的任何窗口都不应当获得焦点。 输入场景
默认输入场景
系统通过默认给每个线程一个 输入场景。该场景为该线程所有对IME不敏感的窗口共享。
将输入场景与窗口相关联
应用程序的窗口将其窗口句柄与 输入场景相关联来维持IME状态,包括当前工作字串。一旦应用程序将 输入场景与窗口句柄相关联,系统在窗口激活时将自动选择该场景。利用此特征,应用程序将不需象在Windows 3.1下那样进行复杂的获得、放弃焦点的处理。
使用输入场景
当应用程序或系统创建新的 输入场景时,系统准备新的 输入场景。新的 输入场景已经包括IMCC,即由hCompStr、hCandInfo、hGuideLine、hPrivate和hMsgBuf组成的IMC的组件。IME基本上不需创建 输入场景和 输入场景的组件。IME可以通过锁定它们来改变其大小并能获得指向组件的指针。
访问HIMC
要访问 输入场景,IME必须调用ImmLockIMC来获得指向 输入场景的指针。ImmLockIMC递增IMC的IMM锁定记数,而ImmUnlockIMC则递减它。
访问HIMCC
要访问 输入场景的一个组件,IME必须调用ImmLockIMCC来获得指向IMCC的指针。ImmLockIMCC递增IMCC的IMM锁定记数,而ImmUnlockIMC则递减它。ImmReSizeIMCC可以将IMCC调整至特定的大小。 有时,IME可能需要自己创建 输入场景的一个组件。在这种情况下,IME可以调用ImmCreateIMCC函数并获得IMCC的句柄。该IMCC可以是INPUTCONTEXT结构的成员(hCompStr、hCandInfo、hGuideLine、hPrivate或hMsgBuf)。 ImmDestroyIMCC销毁 输入场景的一个组件。
怎样使用输入场景
以下例子显示如何使用 输入场景: LPINPUTCONTEXT lpIMC;LPCOMOSITIONSTRING lpCompStr;HIMCC hMyCompStr;if (hIMC) { // It is not NULL context. lpIMC = ImmLockIMC(hIMC); if (!lpIMC) { MyError( "Can not lock hIMC"); return FALSE; } // Use lpIMC->hCompStr. lpCompStr = (LPCOMPOSITIONSTRING) ImmLockIMCC(lpIMC->hCompStr); // Access lpCompStr. ImmUnlockIMCC(lpIMC->hCompStr); // ReSize lpIMC->hCompStr. if (!(hMyCompStr = ImmReSizeIMCC(lpIMC->hCompStr,dwNewSize)) { MyError("Can not resize hCompStr"); ImmUnlockIMC(hIMC); return FALSE; } lpIMC->hCompStr = hMyCompStr; ImmUnlockIMC(hIMC);}
生成消息
IME需要生成IME消息。当IME开始一次回话时,IME必须生成WM_IME_STARTCOMPOSITION消息。如果IME改变工作字符串,IME必须生成WM_IME_COMPOSITION消息。从IME来的事件是通过向与 输入场景相关联的窗口生成消息来实现的。IME基本上是利用ImeToAsciiEx函数的参数所提供的lpdwTransKey缓冲区来生成消息的。当ImeToAsciiEx被调用时,IME将消息放进lpdwTransKey缓冲区。即使ImeToAsciiEx未被调用,IME还是能够利用 输入场景的消息缓冲区向与 输入场景相关联的窗口生成消息。 输入场景以内存块句柄的形式拥有消息缓冲区。IME将消息放进由消息缓冲区句柄提供的内存块中。然后,IME调用ImmGenerateMessage。ImmGenerateMessage函数将存储于消息缓冲区里的消息发送到适当的窗口。
状态、按键组合和候选窗口
状态、按键组合和候选窗口构成了IME程序的用户界面。状态窗口的出现就表示IME程序已经打开,并向用户提供各种转换方式的提示。每当用户输入文本时,按键组合窗口就会出现,并根据采用的转换方式显示用户输入的文本或按键组合对应的转换文本。候选窗口是和按键组合窗口一起出现的,用以列出所有候选输入文本或按键组合窗口中的键盘字符。用户可以通过翻动候选内容来查找选择他们希望输入的文本,当选择输入完成之后,IME程序控制焦点再次返回到按键组合窗口上。当用户录入完需要使用输入法来录入的文本后,就可以把IME程序的窗口关掉了。
IME程序向应用程序发送用户选定的输入文本的工作是通过WM_IME_CHAR或WM_IME_COMPOSITION/GCS_RESULT消息来完成的。如果客户端程序没有处理这些消息,则系统会用DefWindowProc函数来将它们转换为一个或多个WM_CHAR消息。
缺省情况下,复杂文本输入所需的状态、按键组合和候选窗口的创建和管理工作是由操作系统负责完成的。对于大多数应用程序来说,采用这种默认的IME处理方式就足以满足要求了。这种程序的IME支持完全依赖于操作系统,因为它们不了解操作系统执行IME窗口管理的大多数工作过程,所以被称为“IME无知”的程序。
对应的,能够参与到IME窗口的创建和管理工作中的程序则被称之为“IME敏感”的程序。这类程序可能需要控制缺省IME窗口的操作、位置和外观,这就需要向这些窗口发送消息或拦截并处理从这些窗口发出的消息。在某些情况下,应用程序还可能创建它们自己的IME窗口并为它们自己定制的状态、按键组合和候选窗口提供完整的处理过程。
IME窗口类
IME窗口类是一个预设的系统全局类,它定义了标准IME窗口的外观和行为,有点类似于应用程序使用CreateWindowEx函数创建的公共控件窗口类。跟静态窗口一样,IME窗口不能自己响应用户的输入动作。当用户输入时,系统会向IME发送特定的消息,然后由IME或应用程序处理这些控件消息来实现对用户行为的响应。
有时,IME感知的程序会自己使用IME类来创建定制的IME窗口。相较于缺省的IME窗口处理过程,使用定制窗口的好处是可以控制窗口配置。
IME消息
当某些影响IME窗口的事件发生时,操作系统会向应用程序的窗口处理过程发送IME窗口消息。比如,当窗口激活时系统会给应用程序发送WM_IME_SETCONTEXT消息。IME无知的程序会忽略此消息,交由DefWindowProc函数来处理――发送给相应的缺省IME窗口(即当前程序激活的那个IME窗口)。而IME感知的程序则会自己处理这些消息或把这些消息传递给自己的IME窗口。
应用程序可以以命令的形式直接实现对IME窗口的控制,比如使用WM_IME_CONTROL消息来改变按键组合窗口的位置。IME使用通知类消息WM_IME_COMPOSITION来告知应用程序按键组合字符串已改变,而通常情况下IME窗口状态的改变则是由WM_IME_NOTIFY消息来通知的。
输入环境
所谓“输入环境”其实是系统内由IME管理的一个数据结构,其中包含了供IME窗口使用的IME状态信息。缺省情况下,操作系统会为每一个线程创建并分配一个输入环境。在线程中,缺省的输入环境是一个共享资源并关联到每一个新建的窗口。
如果想在应用程序中获取或设置IME信息的话,必须先取得与一个特定窗口相关联的输入环境的句柄。可以通过ImmGetContext函数来得到这个句柄,然后才能通过它调用IMM函数来获取和设置IME属性(比如按键组合窗口的样式、组合形式以及状态窗口的位置等)。如果应用程序不再需要使用输入环境,就应该及时使用ImmReleaseContext函数释放它。
因为缺省的输入环境是一个共享资源,应用程序对它作出的任何更改都会影响到线程中所有的窗口。不过,应用程序可以通过创建自己的输入环境并关联到线程中一个或多个特定的窗口上去,从而避免可能会影响到不相干窗口的输入环境的情况。应用程序对特定输入环境的更改只作用于与该环境相关联的那些窗口。
提供输入环境创建功能的函数是ImmCreateContext,把环境分配给一个窗口的工作则需要通过ImmAssociateContext函数来完成,该函数结束时会返回原来与窗口关联的输入环境的句柄,如果应用程序之前没有给这个窗口专门分配一个输入环境,那么函数返回的就是缺省输入环境的句柄。对这个"原先输入环境"返回值的典型利用实例是,应用程序在给窗口分配特定输入环境的同时保存原来的环境句柄,当特定的输入环境不再需要使用的时候再把原先的环境重新分配给窗口。
一旦把一个输入环境分配给了某个窗口,操作系统就会在窗口被激活和获得输入焦点的时候自动选用分配给这个窗口的输入环境。按照IME的运行机制,从此该输入环境中的样式和其他信息都会作用于这个窗口上所发生的键盘输入动作。
应用程序在结束运行之前,必须销毁所有它自己定制的输入环境:首先通过ImmAssociateContext函数把线程中所有窗口与专门的输入环境所建立的关联去除掉,然后调用ImmDestroyContext函数把撤销了一切关联的输入环境销毁。
组合字符串
组合字符串是指当前显示在按键组合窗口中的文本,这个文本就是IME最终要用来转换成候选字符的文本。组合字符串可以由一个或多个"子串"组成,子串是可以给IME用来转换成一个候选字符的最小输入字符组合。应用程序可以通过 ImmGetCompositionString 和 ImmSetCompositionString 函数来获取或设置组合字符串。
当用户在按键组合窗口中 输入文本的时候,IME就会跟踪组合字符串的状态,包括:属性信息、子串信息、按键信息和 输入光标位置等。应用程序可以通过 ImmGetCompositionString函数来实时地获得这些 输入字符组合过程状态。
属性信息是一个字节数组,用来表示组合字符串中的字符的状态。一个子串中的所有字符必须具有相同的属性。数组的每个字节单元对应于组合字符串中的每个字节,包括组合字符串中可能出现的每个双字节字符上的引导字节和尾随字节。对于属性数组中每个字节单元来说,其取值的第0到3位可以是下列常量中的任一组合。
常量 | 含义 |
ATTR_INPUT | 用户输入字符,需要通过IME进行转换。 |
ATTR_INPUT_ERROR | 一个IME无法转换的错误字符,IME可能无法接受对某些字符组合进行转换。 |
ATTR_TARGET_CONVERTED | 用户选定然后由IME转换过的字符。 |
ATTR_CONVERTED | 已经由IME转换过的字符。 |
ATTR_TARGET_NOTCONVERTED | 将要被转换的字符。用户已经选定了这个字符,但IME还没有开始进行转换。 |
ATTR_FIXEDCONVERTED | 不足以让IME进行转换的字符组合。 |
组合字符串状态中的子串信息是一个双字(32位)单元数组,用来表示每个子串所在的位置。数组开始部分的每个单元对应一个子串,最后一个单元的值用来表示整个组合字符串的长度。子串所在位置以其开始的字符在组合字符串中的位置来表示,字符在组合串中的位置值从0开始计算。假设一个组合字符串中有两个子串的话,那么子串信息数组中就应该有三个单元:第一个单元值为0;第二个单元的值表示第二个子串在组合串中的偏移;第三个单元值是组合串的长度。注意以上所说的子串位置和组合字符串长度的值均以字节为单位――也就是说假设应用程序是以ANSI的方式处理文本的。对于以Unicode的方式处理文本的程序来说,因为Windows采用UTF-16编码格式作为Unicode内码实现方案,所以计算子串位置和组合字符串长度的值以UTF-16编码单元(即很多提供Unicode字符/串数据类型的编程语言里面所说的“宽字符”)为单位。
组合字符串状态中的按键信息就是由用户按下的键盘字符组成的一个空结束字符串。
组合字符串状态中的输入光标位置指的是用户在按键组合窗口中输入组合字符串时,那个用来编辑输入字符的光标所在的位置。它表示光标在当前组合字符串中的位置,从0开始计算。如果这个位置值为0,则表示光标位于当前组合字符串的第一个字符前面;如果这个位置值等于组合串的长度,就表示光标位于组合串最后一个字符的后面;如果这个位置值为1,则光标不出现。对于ANSI程序,以上提及的位置和长度值以字节为单位;而对于Unicode程序,则以Unicode字符为单位。
应用程序可以通过ImmSetCompositionString函数来设置组合字符串或其状态,需要注意的是当做出这些更改之后必须相应更新按键组合窗口的显示――这个函数允许程序向窗口发送一则通知类消息。通常,应用程序在对组合字符串状态作出多项改变的过程中不会逐项向窗口报告,而是在设置最后一项状态的函数调用中才向按键组合窗口发送一则“总的”通知消息。
在使用标准编辑控件的时候,有两个消息可以用来改变IME对组合字符串状态更新的处理过程--它们是EM_GETIMESTATUS和EM_SETIMESTATUS。更详细的内容请查阅标准编辑控件主题。
候选列表
在程序中,候选列表被表现为一个 CANDIDATELIST结构,该结构包含了一个字符串数组,这些字符串就是提供给用户选择 输入的候选文字内容。应用程序可以通过 ImmGetCandidateListCount和 ImmGetCandidateList函数获得候选列表中的信息。
热键
用户通过热键可以快速地更换 输入法的 输入模式或切换到另一个 输入法。虽然操作系统没有提供给应用程序添加 输入法热键的功能,但我们可以用 ImmSimulateHotKey函数来启用自己的热键响应例程。
在下一节 HexToUnicode输入法当中,我们将介绍一个在十六进制编码点和Unicode字符之间进行转换的 输入法。
HexToUnicode输入法
多格式编辑控件从3.0版开始内置了一种HexToUnicode 输入法,它允许用户通过两种热键来执行十六进制编码点和Unicode字符之间的转换。
第一种热键转换方法是:用户 输入字符的十六进制编码点,然后按下组合键ALT+X把 输入光标位置上的编码数字串替换成相应的Unicode字符。如果 输入光标当前位置上设置的字体不支持转换出来的字符,控件会自动选择系统中一个合适的字体来显示它。另外,如果当前 输入光标位置上的不是一个有效的十六进制编码点数字串的话,则按下ALT+X组合键就是把这个位置上的那个字符替换成它的十六进制Unicode编码点数字串。这个方法的另一个作用就是可以用来确定当前系统中是否缺少可以显示某个字符的字体资源。如果一个数字串在转换成字符时有可能造成歧义的话,用户可以先选中要转换的那些数字符后再进行转换,以确保得到想要的转换结果。使用ALT+X热键时需要注意一个问题就是很多软件会把它作为执行"退出"的键盘快捷方式。像Office则不提供退出的键盘快捷方式,而只是在文件菜单上提供了一个"退出"菜单项。
第二种热键转换方法是使用数字小键盘:按下ALT键不放,然后在数字小键盘上 输入字符的十进制编码点(大于255)。相对于第一种方法,这个方法不太好用,因为在整个编码点 输入过程中用户都没有办法确认自己 输入的是否正确并修正。
输入法编辑器的使用
关于如何使用IME的问题将分为如下几节进行讨论:
· WM_IME_COMPOSITION消息的处理
· IME和Unicode
· IME自动更正功能的使用
· IME感知的多线程应用程序的开发
WM_IME_COMPOSITION消息的处理
应用程序通过处理 WM_IME_COMPOSITION消息对 lParam参数的位进行测试并调用 ImmGetCompositionString函数来获得组合字符串的信息或数据。下面这段示例代码演示了如何编程实现上述处理过程,注意在从IME处获取组合字符串前必须为字符串分配适当的内存:
[Copy to clipboard]
CODE:
HIMC hIMC;
HWND hWnd;
DWORD dwSize;
HGLOBAL hstr;
LPSTR lpstr;
case WM_IME_COMPOSITION:
if (lParam & GCS_RESULTSTR)
{
hIMC = ImmGetContext(hWnd);
If (!hIMC)
MyError(ERROR_NULLCONTEXT);
// Get the size of the result string.
dwSize = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);
// increase buffer size for terminating null character,
// maybe it is in UNICODE
dwSize += sizeof(WCHAR);
hstr = GlobalAlloc(GHND,dwSize);
if (hstr == NULL)
MyError(ERROR_GLOBALALLOC);
lpstr = GlobalLock(hstr);
if (lpstr == NULL)
MyError(ERROR_GLOBALLOCK);
// Get the result strings that is generated by IME into lpstr.
ImmGetCompositionString(hIMC, GCS_RESULTSTR, lpstr, dwSize);
ImmReleaseContext(hWnd, hIMC);
// add this string into text buffer of application
GlobalUnlock(hstr);
GlobalFree(hstr);
}
IME和Unicode
Windows 98/Me、Windows NT/2000/XP:在原先支持ANSI界面的基础上,Windows增加了IME对Unicode界面的支持。Windows 98/Me支持除了 ImmIsUIMessage之外所有的Unicode函数,同时在Windows 98/Me系统中所有的消息都是基于ANSI的。因为Windows 98/Me不支持Unicode消息,应用程序可以使用 ImmGetCompositionString函数从Windows 98/Me上基于Unicode的IME处获取Unicode字符。
IME及其对于Unicode的处理有两个需要注意的事项。其一是Unicode版本的IME函数在计算缓冲区所需的字节数时是以16位Unicode字符为考虑的;其二是IME通常从 WM_CHAR和 WM_IME_CHAR消息中接收到的是Unicode字符而不是DBCS字符。
Windows NT/2000/XP:应用程序可以使用 RegisterClassW创建窗口,让它从WM_CHAR和WM_IME_CHAR消息的 wParam参数处获得Unicode(不是DBCS)字符。该功能不能用在Windows 95/98/Me上。
IME自动更正功能的使用 IME中有一项被称为“自动更正”的特性。通常情况下,IME只会在候选列表中列出与用户键入的组合字符串对应的文字,而自动更正功能则允许IME根据上下文关系“推测”出一个或更多其它的候选文字内容。有三种可供选择的联想类型:简单、标准和增强。 自动更正功能常用于当用户发现组合字符串对应出并非自己所需要的文字内容的情况下--这是指在支持词语/句子的输入法中,如果转换出的是错误的词语或句子的话,用户可以选中转换出错的文字并从菜单中选择“自动更正”。IME会根据词语或句子的上下文关系自动选择最合适的文本来进行更正。对于应用程序来说,可以利用ImmSetCompositionString函数来实现自己的“自动更正”功能。 IME感知的多线程应用程序的开发 对于线程调用来说,IMM会对它进行检查以鉴定它是否创建了一个专用输入法环境句柄(hIMC)或窗口句柄(hWnd)。如果线程没有创建任何句柄,则IMM函数调用会失败,并通过GetLastError 函数返回ERROR_INVALID_ACCESS错误。 注意:当前的IMM体系结构并没有提供同步访问IMM句柄的机制。 如果要在应用程序中使用线程鉴识检查的话,则必须遵守以下的两项规定: • (线程)不要访问另一个线程创建的输入环境。 • (线程)不要把另一个线程创建的输入环境关联到自己的窗口上。 输入法编辑器(IME)编程指南 以下是IME编程中需要用到的几项基本组成元素: • IME函数 • IME消息 • IME命令 • IME结构 • IME常量 IME函数 本节列出了所有IME函数。
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
IME消息 以下列出IME中用到的消息。 WM_IME_CHAR(IME得到了转换结果中的一个字符) WM_IME_COMPOSITION(IME根据用户击键的情况更改了按键组合状态) WM_IME_COMPOSITIONFULL(IME检测到按键组合窗口的区域无法继续扩展) WM_IME_CONTROL(由应用程序直接向IME发出控制请求) WM_IME_ENDCOMPOSITION(IME完成了对用户击键情况的组合) WM_IME_KEYDOWN(检测到“键盘上的某键被按下”的动作,同时在消息队列中保留该消息) WM_IME_KEYUP(检测到“键盘上的某键已弹起”的动作,同时在消息队列中保留该消息) WM_IME_NOTIFY(IME窗口发生了改变) WM_IME_REQUEST(通知:IME需要应用程序提供命令和请求信息) WM_IME_SELECT(操作系统将改变当前IME) WM_IME_SETCONTEXT(输入焦点转移到了某个窗口上) WM_IME_STARTCOMPOSITION(IME准备生成转换结果) IME命令 以下列出IME中用到的命令(控制消息)。 IMC_CLOSESTATUSWINDOW(隐藏状态窗口) IMC_GETCANDIDATEPOS(获取候选窗口的位置) IMC_GETCOMPOSITIONFONT(获取用来显示按键组合窗口中的文本的逻辑字体) IMC_GETCOMPOSITIONWINDOW(获取按键组合窗口的位置) IMC_GETSTATUSWINDOWPOS(获取状态窗口的位置) IMC_OPENSTATUSWINDOW(显示状态窗口) IMC_SETCANDIDATEPOS(设置候选窗口的位置) IMC_SETCOMPOSITIONFONT(设置用来显示按键组合窗口中的文本的逻辑字体) IMC_SETCOMPOSITIONWINDOW(设置按键组合窗口的样式) IMC_SETSTATUSWINDOWPOS(设置状态窗口的位置) IMN_CHANGECANDIDATE(IME通知应用程序:候选窗口中的内容将改变) IMN_CLOSECANDIDATE(IME通知应用程序:候选窗口将关闭) IMN_CLOSESTATUSWINDOW(IME通知应用程序:状态窗口将关闭) IMN_GUIDELINE(IME通知应用程序:将显示一条出错或其他信息) IMN_OPENCANDIDATE(IME通知应用程序:将打开候选窗口) IMN_OPENSTATUSWINDOW(IME通知应用程序:将创建状态窗口) IMN_SETCANDIDATEPOS(IME通知应用程序:已结束候选处理同时将移动候选窗口) IMN_SETCOMPOSITIONFONT(IME通知应用程序:输入内容的字体已更改) IMN_SETCOMPOSITIONWINDOW(IME通知应用程序:按键组合窗口的样式或位置已更改) IMN_SETCONVERSIONMODE(IME通知应用程序:输入内容的转换模式已更改) IMN_SETOPENSTATUS(IME通知应用程序:输入内容的状态已更改) IMN_SETSENTENCEMODE(IME通知应用程序:输入内容的语句模式已更改) IMN_SETSTATUSWINDOWPOS(IME通知应用程序:输入内容中的状态窗口的位置已更改) IMR_CANDIDATEWINDOW(通知:选定的IME需要应用程序提供有关候选窗口的信息) IMR_COMPOSITIONFONT(通知:选定的IME需要应用程序提供有关用在按键组合窗口中的字体的信息) IMR_COMPOSITIONWINDOW(通知:选定的IME需要应用程序提供有关按键组合窗口的信息) IMR_CONFIRMRECONVERTSTRING(通知:IME需要应用程序更改RECONVERTSTRING结构) IMR_DOCUMENTFEED(通知:选定的IME需要从应用程序那里取得已转换的字符串) IMR_QUERYCHARPOSITION(通知:选定的IME需要应用程序提供有关组合字符串中某个字符的位置信息) IMR_RECONVERTSTRING(通知:选定的IME需要应用程序提供一个用于自动更正的字符串) IME编程中需要用到的数据结构 这里列了所有在使用输入法编辑器函数和消息时需要用到的数据结构。 CANDIDATEFORM(描述候选窗口的位置信息) CANDIDATELIST(描述有关候选列表的信息) COMPOSITIONFORM(描述按键组合窗口的样式和位置信息) IMECHARPOSITION(描述按键组合窗口中的字符的位置信息) IMEMENUITEMINFO(描述IME菜单项的信息) RECONVERTSTRING(定义用于IME自动更正功能的字符串) REGISTERWORD(描述一个要注册的读入信息或文字内容) STYLEBUF(描述样式的标识符和名称) IME常量 这里列出了所有在使用输入法编辑器函数和消息时需要用到的常量。 • IME转换模式常量 • IME按键组合字符串常量 • IME热键标识常量 • IME句型模式常量 • IMMEscape函数常量 |