首页 > 代码库 > 【转载】VC IME 通信

【转载】VC IME 通信

文本输入框作为一个最基本的UI控件,被众多UI框架默认支持。Windows下最简单的就是CEditWTL封装),也有更为复杂的CRichEditWTL封装)。文本输入框是基本控件中最难实现的控件之一,估计这也是Chrome浏览器For Windows一直使用原生文本输入框封装,而不是自行实现的原因。很多情况下,默认的输入框足够使用,对于一些简单的限制(例如只能输入数字)也可以通过对原生控件进一步封装实现。不过如果要深度定制就估计只有自行实现了。

      如果只要求支持ASCII吗,那就轻而易举了,直接监听WM_CHARFor Windows消息即可。不过这个世界语言太多了,作为一个红朝子民,至少得支持汉语吧!可是汉语并不能通过监听WM_CHAR获得。所以下面就来探讨一下Windows操作系统下中文的输入。

      目前主流操作系统都是默认支持ASCII输入,而对于其他语言的输入则必须借助输入法来实现。为了支持多语言输入,主流的UI框架都会集成输入法嵌入的支持模块,而对输入法的友好程度也反映了该UI框架的水平。估计很多Linux用户Gnome环境)以前会很烦在用QT实现的Opera浏览器中用输入法。

      Windows的输入框架在这里(http://blog.csdn.net/dengting/archive/2002/08/17/14638.aspx )讲的比较详细,对于其他语言的输入,Windows并不是通过WM_CHAR消息传递,而是通过输入法发出的WM_IME_CHAR消息提供,所以我们可以监听这个消息来获得相应的汉字。不过在发送WM_IME_CHAR消息之前,Windows会先发送WM_IME_COMPOSITION消息。大概流程如下:

  1. 用户按下键盘,Windows发送WM_CHAR

  2. 如果当前使用输入法输入,则让输入法重组,获得第三方语言字符(串)

  3. 输入法发送WM_IME_COMPOSITION、WM_IME_CHAR等消息。UI控件截取这些消息,获得汉字。

      Windows并不会同时发送WM_IME_COMPOSITION、WM_IME_CHAR和WM_CHAR消息,而是依次发送。首先会发送WM_IME_COMPOSITION消息,UI控件可以通过ImmGetCompositionString函数获得输入的字符串。如果希望继续发送后续消息,则调用Windows默认的处理函数。此时Windows会接着发送WM_IME_CHAR消息,UI控件可以通过它的参数获得单个中文字符,如果希望继续发送后续消息,则调用Windows默认的处理函数。此时Windows会接着发送WM_CHAR消息。很显然,只有经的上层同意,Windows才会发送后续的消息。

      既然对于ASCII输入和中文输入需要监听不同的事件,那就必须有一个办法判断用户当前是使用英文键盘还是使用输入法输入。这可以通过Windows API函数ImmIsIME判断。此外关于中英文切换摘录了一段如下:

【http://topic.csdn.net/t/20020509/02/707135.html 】

中英文输入法切换?    
function   boolean   ImmSimulateHotKey   (UnsignedLong   hWnd,   
UnsingedLong   dwHotKeyID)   library   “IMM32.dll“   
function   unsignedlong   GetKeyboardLayout   (unsignedlongwLayout)library   “user32.dll“   
function   boolean   ImmIsIME(unsignedLong   hklKeyboardLayout)library   “IMM32.DLL“  

英文输入法切换   

constant int IME_THotKey_IME_NonIME_Toggle=112uint hklCurrentunsignedlong hnd hklCurrent = GetKeyboardLayout(0)if ImmIsIME(hklCurrent) then hnd = Handle(parent) ImmSimulateHotKey(hnd,IME_THotKey_IME_NonIME_Toggle)end if

中文输入法切换   

constant int IME_THotKey_IME_NonIME_Toggle=112uint hklCurrentunsignedlong hnd hklCurrent = GetKeyboardLayout(0)if not ImmIsIME(hklCurrent) then hnd = Handle(parent) ImmSimulateHotKey(hnd,IME_THotKey_IME_NonIME_Toggle)end if

 

      写了一个sample练练笔(WTL实现),有兴趣可以瞄瞄,有什么错误欢迎指正。支持ascii和unicode,不过只支持ascii可见字符、汉字和退格键,没有实现光标,方向键等。此外该sample同时处理了WM_IME_COMPOSITION消息和WM_IME_CHAR消息,所以用户输入一个汉字串后会显示两个相同的,实际应用应该选择其中之一。考虑到ascii方式汉字占用字节和英文字符不一样,所以退格键需要特殊处理,见源码。

      使用方式示例:

InputStatic istatic_istatic_.Create(m_hWnd,CRect(0,250,500,400),NULL,WS_CHILD | WS_VISIBLE);istatic_.SetFocus();

      之所以要SetFocus,是因为sample使用CStatic,该控件无法获得焦点。(注意:该sample仅仅只是一个测试代码,不要拿来和真正文本输入框对比)

(InputStatic.h)

/** Author: 邱金武<qiujinwu456@gmail.com>*/#ifndef INPUT_STATIC__H_#define INPUT_STATIC__H_#include <string>#include <atlbase.h>#include "atlapp.h"#include <atlwin.h>#include "atlctrls.h"#ifndef _UNICODEtypedef std::string ISString;#elsetypedef std::wstring ISString;#endifclass InputStatic: public CWindowImpl< InputStatic ,CStatic>{public: InputStatic(const ISString & initStr = _T("")); BEGIN_MSG_MAP( NormalButton ) MESSAGE_HANDLER(WM_CHAR, OnChar) MESSAGE_HANDLER(WM_IME_CHAR, OnImeChar) MESSAGE_HANDLER(WM_IME_COMPOSITION, OnImeCompositionChar) END_MSG_MAP()private: LRESULT OnChar(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); LRESULT OnImeChar(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); LRESULT OnImeCompositionChar(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/,BOOL& bHandled); ISString m_content_;};#endif

(InputStatic.cpp)

/** Author: 邱金武<qiujinwu456@gmail.com>*/#include "InputStatic.h"#ifndef _UNICODE//见: http://blog.chinaunix.net/u2/70445/showart_1133335.htmlstatic bool endWithGBCode(const std::string & strIn){ unsigned char ch1; unsigned char ch2; if (strIn.size() >= 2) { ch1 = (unsigned char)strIn.at(strIn.size() - 1); ch2 = (unsigned char)strIn.at(strIn.size() - 2);#ifdef USE_GB2312 //GB2312 if (ch1>=176 && ch1<=247 && ch2>=160 && ch2<=254)#else //GBK if (ch1>=129 && ch1<=254 && ch2>=64 && ch2<=254)#endif return true; else return false; } else return false;}#endifInputStatic::InputStatic(const ISString & initStr): m_content_(initStr){}LRESULT InputStatic::OnChar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled){ //处理可显示ascii字符 if(wParam >= ‘ ‘ && wParam <= ‘~‘) { this->m_content_ += wParam; this->SetWindowText(this->m_content_.c_str()); } //处理退格键 else if(VK_BACK == wParam) { if(!m_content_.empty()) {#ifndef _UNICODE //考虑到ansi方式中文字符和ascii具有不同的长度,所以这里需要特殊处理 if(endWithGBCode(m_content_)) m_content_ = m_content_.substr(0,m_content_.size() - 2); else m_content_ = m_content_.substr(0,m_content_.size() - 1);#else m_content_ = m_content_.substr(0,m_content_.size() - 1);#endif this->SetWindowText(m_content_.c_str()); } } return 1;}LRESULT InputStatic::OnImeCompositionChar(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/,BOOL& bHandled){ //继续发送消息,否则收不到WM_IME_CHAR消息 bHandled = FALSE; HIMC hImc; DWORD dwSize; TCHAR *Buf; hImc = ImmGetContext(GetActiveWindow()); dwSize = ImmGetCompositionString(hImc, GCS_RESULTSTR, NULL, 0); if(dwSize) { Buf = reinterpret_cast<TCHAR*>(new char[dwSize + sizeof(TCHAR)]); memset(Buf,0,dwSize + sizeof(TCHAR)); ImmGetCompositionString(hImc, GCS_RESULTSTR, (LPVOID)Buf, dwSize); this->m_content_ += Buf; delete [] Buf; this->SetWindowText(m_content_.c_str()); } ImmReleaseContext(GetActiveWindow(), hImc); return 1;}LRESULT InputStatic::OnImeChar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled){ //继续发送消息,否则收不到WM_CHAR消息 bHandled = FALSE;#ifndef _UNICODE char imeChar[3]; imeChar[0] = (char)(wParam>>8); imeChar[1] = (char)wParam; imeChar[2] = ‘\0‘;#else wchar_t imeChar = wParam;#endif this->m_content_ += imeChar; this->SetWindowText(m_content_.c_str()); return 1;}

      如果需要做类似密码输入框的处理,不管使用什么输入法,总之只接收英文字符。此时可以将WM_IME_COMPOSITION、WM_IME_CHAR消息的处理函数留空,并使用Windows默认的处理函数。