在开发输入法应用的时候,我们需要让候选栏时刻跟踪输入光标的位置,来进行输入。但候选栏定位不准,一直会困扰输入法的开发者。windows老的输入法框架imm在部分场景下比如chrome应用中会定位不准,而新的输入法框架TSF在部分应用中也会定位不准比如Notepad++等应用。
解决定位不准的问题,通常有两种方案
1.通过应用获取输入光标的位置然后进行候选定位
2.通过TSF框架预先上屏占位符号进行定位
两种方案各有利弊,这里我介绍一下两种方案
1.通过应用的输入光标进行定位,获取光标的方法如下
POINT get_candidates_locate_pos()
try
{
HWND hwnd;
GUITHREADINFO pg;
POINT point = {-1,-1};//光标位置
DWORD dwProcID;
hwnd= GetForegroundWindow();//获取当前激活应用
//根据窗口句柄获取线程对应的ID
DWORD ForeThreadId = GetWindowThreadProcessId(hwnd, &dwProcID);
pg.cbSize = sizeof(GUITHREADINFO);
::GetGUIThreadInfo(ForeThreadId, &pg);
if (pg.hwndCaret)
{
//获得输入光标的位置
point.x = pg.rcCaret.right;
point.y = pg.rcCaret.bottom;
::ClientToScreen(pg.hwndCaret, &point);
}
else
{
return point;//取不到对应的光标位置
}
return point;
}
catch(exception& e)
{
log(TEXT("%s throw exception:%s"), __FUNCTION__, e.what());
return POINT(-1,-1);
}
这种方案本质就是通过框架和应用获取应用中光标所在的位置通过进程获取输入光标的位置。但是这种方案在部分应用中获取不到对应的光标,比如基于chrome内核和Electron应用,相关应用有115网盘、钉钉等等。所以这种方案是有一定的缺陷的。
方案二
通过预先上屏字符进行定位
如上图所示,预先上屏的字符串就是红框框选的字符。这些字符在候选栏上屏之后会消失。这种方案的优点是通过TSF框架进行预先定位,在大部分场景下定位比较精确。缺点是,会预先上屏一部分字符,遮盖住部分内容,影响对应的视觉效果。
图片中的方框内容在TSF框架里面对应的类是ITfRange,预先上屏占位,但候选上屏的时候字符串会消失。为了避免预先上屏的字符串影响用户的视觉效果可以预先上屏一个空格或者宽度为0的Unicode字符\u200b来进行定位
TSF框架获取输入光标的位置,实际就是通过ITfRange上屏的字符串矩形获取定位.,框架中的调用流程如下所示.
HRESUL GetCandidateCursorPos(_Out_ RECT *lpRect, _Out_ HWND *hwnd)
{
HRESULT hr = S_OK;
BOOL isClipped = TRUE;
CComPtr<ITfContextView> pContextView = nullptr;
//先通过ITfContext获取对应的编辑视图
hr = _ITfContext_ptr->GetActiveView(&pContextView);
if (FAILED(hr))
{
//获取对应的视图失败
return hr;
}
//@1 指定tfEditCookie
//@2 输入法预上屏的字符_pRange
//@3 获取_pRange所在输入文档显示的Rect
//@4 最后一个参数指定位true,则只跟踪被裁剪的pRange//如果预上屏的字符串为空,则获取的定位lpRect可能异常
if (FAILED(hr = pContextView->GetTextExt(_tfEditCookie, _ptfRange,
lpRect, &isClipped)))
{
return hr;
}
pContextView->GetWnd(hwnd);
return S_OK;
}
其实我觉的两种方案都各有优劣,我建议将两种方案组合起来进行使用,在输入法内部预置一份应用的名单,在部分场景下通过应用获取光标的方式进行候选栏定位,其它的场景下通过预先上屏一个空字符来进行定位,这样的话,整体效果是最佳的.当然也不排除随着windows TSF框架的优化,在不久的将来输入法候选栏定位问题已经不再是一个问题了.