如何在游戏中获取输入法候选列表

本文讲述在windows操作系统中如何通过系统提供的输入法接口获取当前输入法的候选列表信息。在全屏游戏或需要自绘输入法候选列表的软件中均需使用此技术。

在阅读之前,请务必了解windows之中包含“Input Method Editor (IME)”“Text Services Framework (TSF)”两套输入法接口。对于使用不同框架的输入法应采用不同方式去获取候选列表。本文重点讲述TSF框架下的输入法后续列表获取。TSF框架的输入法实际是一个COM程序,所以在继续阅读之前,请务必了解COM的工作机制。

1 不得不说的IME候选列表获取方式

在网络上应该能搜出一大片此类文章,在此仅做简要说明。 IME的所有函数都在imm32.dll中实现,函数原型可在中查看。

1.1 在什么时候获取候选列表信息

如需在程序中自绘候选列表,可在程序中响应如下几个重要消息:

         switch( uMsg )

         {

                 case WM_IME_STARTCOMPOSITION:          // 开始编码

                 case WM_IME_COMPOSITION:               // 编码串已更新(显示串更新、上屏串更新、光标位置更改等)

                 case WM_IME_ENDCOMPOSITION:            // 输入结束

                 case WM_IME_NOTIFY:                    // 这个消息应根据 wParam 的值做如下处理

                 switch (wParam)

                 {

                 case IMN_OPENCANDIDATE:          // 打开候选列表

                 case IMN_CHANGECANDIDATE:        // 更新候选列表

                 case IMN_CLOSECANDIDATE:         // 关闭候选列表

                 }

                 break;

         }

1.2 WM_IME_COMPOSITION 处获取显示编码串信息, 关键代码如下:

         case WM_IME_COMPOSITION:

         {

                 LONG lRet;

                 HIMC hIMC;

                 if(hIMC = ImmGetContext(hWnd))

                 {

                          TCHAR szCompStr[256];

                          DWORD dwCursorPos;

                          //获取显示字符串

                          if ( lParam & GCS_COMPSTR )

                          {

                                   lRet = ImmGetCompositionString( hIMC, GCS_COMPSTR, szCompStr, ARRAYSIZE( szCompStr ) ) / sizeof(TCHAR);

                                   szCompStr[lRet] = 0;

                          }

                          //获取显示字符串的属性标记

                          if ( lParam & GCS_COMPSTR )

                          {

                                   lRet = ImmGetCompositionString( hIMC, GCS_COMPATTR, szCompStr, ARRAYSIZE( szCompStr ) ) / sizeof(TCHAR);

                                   szCompStr[lRet] = 0;

                          }

                          //获取显示字符串的光标位置

                          dwCursorPos = ImmGetCompositionString(hIMC, GCS_CURSORPOS, NULL, 0);

                          //获取上屏字符串

                          if ( lParam & GCS_RESULTSTR )

                          {

                                   lRet = ImmGetCompositionString( hIMC, GCS_RESULTSTR, szCompStr, ARRAYSIZE( szCompStr ) ) / sizeof(TCHAR);

                                   szCompStr[lRet] = 0;

                          }

                          ImmReleaseContext(hWnd, hIMC);

                 }

         }

1.3 应在 CHANGECANDIDATE    处获取候选列表信息, 关键代码如下:

         case WM_IME_NOTIFY:

         switch (wParam)

         {

                 case IMN_OPENCANDIDATE:          // 打开候选列表

                 case IMN_CHANGECANDIDATE:        // 更新候选列表

                 {

                          HIMC hIMC;

                          if (hIMC = ImmGetContext(hWnd))

                          {

                                   LPCANDIDATELIST lpCandList = NULL;

                                   DWORD dwIndex = 0;

                                   DWORD dwBufLen = ImmGetCandidateList(hIMC, dwIndex, NULL, 0 );

                                   if ( dwBufLen )

                                   {

                                            lpCandList = (LPCANDIDATELIST)GlobalAlloc(GPTR,dwBufLen);

                                            dwBufLen = ImmGetCandidateList(hIMC, dwIndex, &lpCandList, dwBufLen );

                                   }

 

                                   if ( lpCandList )

                                   {

                                            DWORD dwSelection = lpCandList->dwSelection;    //处于选中状态的候选序号

                                            DWORD dwCount     = lpCandList->dwCount;        //当前页候选数

                                            DWORD dwPageStart = lpCandList->dwPageStart;    //当前页起始序号

                                            DWORD dwPageSize  = lpCandList->dwPageSize;     //当前页容量

 

                                            DWORD i;

                                            for (i = 0; i < dwCount; i++)

                                            {

                                                    LPTSTR lpCandiString = (LPTSTR)((DWORD)lpCandList + lpCandList->dwOffset[i]);  //候选字符串

                                            }

                                            GlobalFree(lpCandList);

                                   }

                                   ImmReleaseContext(hWnd,hIMC);

                          }

                 }

         }

2 TSF候选列表获取方式

TSF框架的候选列表的获取,要求输入法程序必须已经实现UILess模式。

输入法程序在候选列表发生改变时使用ITfUIElementMgr::BeginUIElementITfUIElementMgr::UpdateUIElementITfUIElementMgr::EndUIElement等三个接口函数通知TSF服务程序,TSF服务程序再将此消息转发给所有的UIElement Sink。这期间的过程十分复杂,而且大部分步骤都是由输入法程序和操作系统自己完成的。因此本文不做详细描述,仅对如何获取候选列表做详细说明。

2.1 实现一个Skin用于接收 BeginUIElement UpdateUIElement EndUIElement消息。

在微软DirectX的官方示例代码 \Samples\C++\DXUT\Optional\ImeUi.cpp 中,有一个类名称为 CTsfUiLessMode 可作为参考。下文中的主要代码也是复制于此源代码文件中。

在程序初始时调用 BOOL CTsfUiLessMode::SetupSinks() ,以创建ITfUIElementSink ,关键代码如下:

         BOOL CTsfUiLessMode::SetupSinks()

         {

                 // ITfThreadMgrEx is available on Vista or later.

                 HRESULT hr;

                 hr = CoCreateInstance(CLSID_TF_ThreadMgr,NULL, CLSCTX_INPROC_SERVER, __uuidof(ITfThreadMgrEx),  (void**)&m_tm);

                 if (hr != S_OK)

                 {

                          return FALSE;

                 }

 

                 // ready to start interacting

                 TfClientId cid;// not used

                 if (FAILED(m_tm->ActivateEx(&cid, TF_TMAE_UIELEMENTENABLEDONLY)))

                 {

                          return FALSE;

                 }

 

                 // Setup sinks

                 BOOL bRc = FALSE;

                 m_TsfSink = new CUIElementSink();

                 if (m_TsfSink)

                 {

                          ITfSource *srcTm;

                          if (SUCCEEDED(hr = m_tm->QueryInterface(__uuidof(ITfSource), (void **)&srcTm)))

                          {

                                   // Sink for reading window change

                                   if (SUCCEEDED(hr = srcTm->AdviseSink(__uuidof(ITfUIElementSink), (ITfUIElementSink*)m_TsfSink, &m_dwUIElementSinkCookie)))

                                   {

                                            // Sink for input locale change

                                            if (SUCCEEDED(hr = srcTm->AdviseSink(__uuidof(ITfInputProcessorProfileActivationSink), (ITfInputProcessorProfileActivationSink*)m_TsfSink, &m_dwAlpnSinkCookie)))

                                            {

                                                    if (SetupCompartmentSinks())// Setup compartment sinks for the first time

                                                    {

                                                             bRc = TRUE;

                                                    }

                                            }

                                   }

                                   srcTm->Release();

                          }

                 }

                 return bRc;

         }

2.2 CUIElementSink::BeginUIElement CUIElementSink::UpdateUIElement  中响应来自输入法的候选列表更新操作

DX官方代码中,会首先尝试获取ITfReadingInformationUIElement接口,再尝试获取ITfCandidateListUIElement接口。但一般情况下, TSF输入法中会主要仅实现ITfCandidateListUIElement接口。

BeginUIElement()UpdateUIElement  ()中获取ITfCandidateListUIElement接口,并使用此接口获取候选列表信息, 关键代码如下:

         if (SUCCEEDED(pElement->QueryInterface(__uuidof(ITfCandidateListUIElement), (void **)&pcandidate)))

         {

                 UINT uIndex = 0;

                 UINT uCount = 0;

                 UINT uCurrentPage = 0;        //当前页序号

                 UINT *IndexList = NULL;       //候选列表页索引

                 UINT uPageCnt = 0;

                 DWORD dwPageStart = 0;

                 DWORD dwPageSize = 0;

                 BSTR bstr;

 

                pcandidate->GetSelection(&uIndex);            //获取当前选中状态的候选序号(可设置高亮显示,一般为第一候选)

                 pcandidate->GetCount(&uCount);                //当前候选列表总数

                 pcandidate->GetCurrentPage(&uCurrentPage);    //当前候选列表所在的页

                 g_dwSelection = (DWORD)uIndex;

                 g_dwCount = (DWORD)uCount;

                 g_bCandList = true;

                 g_bReadingWindow = false;

 

                 pcandidate->GetPageIndex(NULL, 0, &uPageCnt); //获取候选列表页每一页对应的起始序号

                 if(uPageCnt > 0)

                 {

                          IndexList = (UINT *)ImeUiCallback_Malloc(sizeof(UINT)*uPageCnt);

                          if(IndexList)

                          {

                                   pcandidate->GetPageIndex(IndexList, uPageCnt, &uPageCnt);

                                   dwPageStart = IndexList[uCurrentPage];

                                   dwPageSize = (uCurrentPage < uPageCnt-1) ?

                                            min(uCount, IndexList[uCurrentPage+1]) - dwPageStart:

                                   uCount - dwPageStart;

                          }

                 }

 

                 g_uCandPageSize = min(dwPageSize, MAX_CANDLIST);

                 g_dwSelection = g_dwSelection - dwPageStart;

 

                 memset(&g_szCandidate, 0, sizeof(g_szCandidate));

                 for (UINT i = dwPageStart, j = 0; (DWORD)i < g_dwCount && j < g_uCandPageSize; i++, j++)

                 {

                          if (SUCCEEDED(pcandidate->GetString( i, &bstr )))  //获取候选列表的第i个候选串

                          {

                                   if(bstr)

                                   {

#ifndef UNICODE

                                            char szStr[COUNTOF(g_szCandidate[0])*2];

                                            szStr[0] = 0;

                                            int iRc = WideCharToMultiByte(CP_ACP, 0, bstr, -1, szStr, sizeof(szStr), NULL, NULL);

                                            if (iRc >= sizeof(szStr))

                                            {

                                                    szStr[sizeof(szStr)-1] = 0;

                                            }

                                            ComposeCandidateLine( j, szStr );

#else

                                            ComposeCandidateLine( j, bstr );

#endif

 

                                            // 应注意TSF框架要求输入内部必须使用SysAlloc() 分配候选列表字符串保存空间

                                            //在调用pcandidate->GetString 之后,必须使用SysFreeString(bstr)释放

                                            SysFreeString(bstr);

                                   }

                          }

                 }

                 if (GETPRIMLANG() == LANG_KOREAN)

                 {

                          g_dwSelection = (DWORD)-1;

                 }

                 if(IndexList)

                 {

                          ImeUiCallback_Free(IndexList);

                 }

         }

在获取候选列表字符串之前必须首先获取当前页序号(uCurrentPage)候选列表页索引(PageIndex),由于微软一直没有公布TSF框架的详细说明文档, 大家都只能从微软的类成员命名中猜测候选列表页索引(PageIndex)的数据结构以及使用方式。所以pcandidate->GetPageIndex()引起很多程序编写者的误解。在此对处做详细说明:

1.    使用空指针参数调用pcandidate->GetPageIndex(NULL, 0, &uPageCnt);以获取当前候选列表页数, 候选列表页数会被写入到uPageCnt

2.    初始化一个内存块用于保存候选列表每一页的起始序号,内存快大小应为页数*sizof(UINT)  IndexList = (UINT *)ImeUiCallback_Malloc(sizeof(UINT)*uPageCnt);

3.    再次调用pcandidate->GetPageIndex(IndexList, uPageCnt, &uPageCnt);

4.    调用pcandidate->GetString( i, &bstr )) 获取候选列表字符串.此处应注意i的取值,在上面的代码中

如果调用成功,在IndexLis会保存后续列表每一页的序号。本文假设候选列表有16个候选,每页显示5个候选,函数执行完毕时, 各变量会被赋值如下:

uPageCnt = 4            //候选列表有4
IndexList[0] = 0       //候选列表第一页的起始序号为0
IndexList[1] = 5       //候选列表第二页的起始序号为5
IndexList[2] = 10
IndexList[3] = 15

好了,总算写完了。做个小结吧,我写这篇文章的初衷是希望能够为游戏编写者自绘输入法候选列表提供一个指导性帮助。写文章的过程中有如下感受:

1.    微软总是在技术说明文档上省工减料

2.    很多软件设计者、开发者都不会为追究细节深入研究

3.    现在想找点技术文档真的是太难了

 


相关推荐

IME输入法编程心得(转载) 1

原文地址:https://www.cnblogs.com/freedomshe/archive/2012/11/30/ime_learning.html自然语言处理的输入法作业成品没有做出来,但不想再

输入法编程输入法管理器 输入法上下文

完整的文档参考:https://docs.microsoft.com/zh-cn/windows/win32/intl/input-context "输入上下文" 是由 IMM 维

输入法编程可能用的到的API接口 IME结构

输入法管理器:GetSystemMetrics(82) // 常量: SM_IMMENABLED 确定是否已启用 IMMIME开发就是实现类似“输入法名字.ime”这样一个动态库(编译的时候通常将.

输入法的注册、安装和卸载

注册输入法输入法的安装和普通应用程序有一个大的区别是,除了复制文件到安装目录、做一些必要的设置外,还需要向Windows系统注册这个输入法。我们前期一篇博文 TSF(Text Service Fram

QQ拼音输入法智能标点设置 左右括号 左右引号 自动补全

打个左括号 自动出来右括号 虽然有时候很方便但是有时候也会感觉这输入法有点多管闲事啊!!!还是需要啥打啥比较好 在属性设置里 其他设置下面有个 智能标点设置的按钮 点开后 根据需要开启或关闭 智能

输入法管理器(IMM)函数大全(Windows CE 5.0)

imm好像要被弃用了 还是研究 TSF输入法 吧 ,虽然没啥鸟用了 但是还是搬运一下下吧EnumRegisterWordProc此函数是与一起使用的应用程序定义的回调函数ImmEnumRegister

TSF输入法

转自https://blog.csdn.net/yang1fei2/article/details/118977318 TSF 即文本服务框架的英文缩写微软官方文档(文本服务框架) https://l

多多输入法:码键(标点符号)顶屏

软件上面的选项:非编码键(标点符号)顶屏配置文件内部显示名字:是否打开非编码按键顶屏=是如果设置为 是当输入c++的时候,可以正常上屏c++否则 输入c的时候 是正常的,但是再输入++的时候,c就没有

JavaScript设置cookie与获取某个cookie的值

JavaScript设置cookie和读取cookie都是使用 document.cookie 来实现JavaScript设置cookie方法:document.cookie="userna

mysql root用户远登录并获取所有权限

允许root用户在一个特定的IP进行远程登录,并具有所有库任何操作权限,具体操作如下:端口就不多说了 肯定要开放端口然后依次执行下面四条命令即可.在本机先使用root用户登录mysql:mysql -