游標跟隨是IME系統最常見的特性。要實現這一特性,需要擷取IME支援程式(最常見如word,notepad等)中插入符號caret的座標位置。在Windows的IME環境中,可以通過使用IME核心資料結構INPUTCONTEXT的cfCompForm成員來擷取IME宿主程式中文本游標位置。cfCompForm具有如下結構:
typedef tagCANDIDATEFORM { //列表視窗資訊
//由IMC_GETCANDIDATEPOS和IMC_SETCANDIDATEPOS訊息處理
DWORD dwIndex; //列表視窗序號
DWORD dwStyle; //屬性:
//=CFS_CANDIDATEPOS 指定顯示位置
//=CFS_EXCLUDE 不可顯示
//=CFS_DEFAULT 根據需要顯示
POINT ptCurrentPos; //座標位置
REC rcArea; //不可顯示區
} CANDIDATEFORM;
其中ptCurrentPos就是我們需要的游標位置,不過因為這是客戶區的座標,需要轉化為螢幕座標才能使用。要使得IME上下文結構INPUTCONTEXT中已經填入了正確的座標位置,需要實現WM_IME_NOTIFY訊息響應事件。在WM_IME_NOTIFY訊息的子訊息IMN_SETCOMPOSITIONWINDOW(設定編碼視窗訊息)被觸發時,系統會返回正確的座標位置。WM_IME_NOTIFY訊息響應函數類似如下形式:
/*
* IMENotifyHandle():
*
* Handle WM_IME_NOTIFY messages.
*/
LONG IMENotifyHandle(HIMC hUICurIMC, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LONG lRet = 0L;
LPINPUTCONTEXT lpIMC;
if (!(lpIMC = ImmLockIMC(hUICurIMC)))
return 0L;
switch (wParam)
{
case IMN_CLOSESTATUSWINDOW:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_CLOSESTATUSWINDOW/n");
/// hide the status window
g_pStatus->Hide();
break;
case IMN_OPENSTATUSWINDOW:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_OPENSTATUSWINDOW/n");
/// create the status window, but don't show
g_pStatus->Create(hWnd);
break;
case IMN_OPENCANDIDATE:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_OPENCANDIDATE/n");
break;
case IMN_CHANGECANDIDATE:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_CHANGECANDIDATE/n");
break;
case IMN_CLOSECANDIDATE:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_CLOSECANDIDATE/n");
break;
case IMN_SETCONVERSIONMODE:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_SETCONVERSIONMODE/n");
/// repaint the status window
g_pStatus->Repaint();
break;
case IMN_SETSENTENCEMODE:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_SETSENTENCEMODE/n");
break;
case IMN_SETOPENSTATUS:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_SETOPENSTATUS/n");
/// repaint the status window
g_pStatus->Repaint();
break;
case IMN_SETCANDIDATEPOS:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_SETCANDIDATEPOS/n");
break;
case IMN_SETCOMPOSITIONFONT:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_SETCOMPOSITIONFONT/n");
break;
case IMN_SETCOMPOSITIONWINDOW:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_SETCOMPOSITIONWINDOW/n");
/// adjust the postion of comp and cand windows
POINT ptSrc;
SIZE szOffset;
HDC hDC;
ptSrc = lpIMC->cfCompForm.ptCurrentPos;
ClientToScreen(lpIMC->hWnd, &ptSrc);
hDC = GetDC(lpIMC->hWnd);
GetTextExtentPoint(hDC,"A",1,&szOffset);
ReleaseDC(lpIMC->hWnd,hDC);
g_ptTopLeft.x = ptSrc.x + szOffset.cx;
g_ptTopLeft.y = ptSrc.y + szOffset.cy;
break;
case IMN_GUIDELINE:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_GUIDELINE/n");
break;
case IMN_SETSTATUSWINDOWPOS:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_SETSTATUSWINDOWPOS/n");
break;
case IMN_PRIVATE:
TRACE("UIWnd:WM_IME_NOTIFY:IMN_PRIVATE/n");
break;
default:
break;
}
ImmUnlockIMC(hUICurIMC);
return lRet;
}
其中,需要注意的是要確保擷取正確的位置,必須有先發送過WM_IME_STARTCOMPOSITION訊息,這個訊息一般在剛開始輸入新拼音時候發送!關於IME訊息處理可以看MSDN相關文檔。
即便如此,仍然不能保證在所有程式中,IME都能正確地體現游標跟隨,我遇到的情況是在UtraEdit中,上述代碼毫無作用,IMN_SETCOMPOSITIONWINDOW根本就沒有被系統觸發,這時我只好通過GetCaretPos來僥倖地擷取游標位置。