Windows WinAPI:如何定位 IME 组合窗口并处理界面变化?
结论
你的 ImmGetContext → ImmSetCompositionWindow → ImmReleaseContext 调用顺序基本正确。不过,ImmSetCompositionWindow 操作的是组合窗口,无法保证把输入法原有的整套浮动界面原样移到 caret 旁边。
从描述看,定位后只显示组合文字,较符合输入法采用行内组合显示的情况。但材料中没有可查看的截图,也没有说明具体输入法,因此还无法判断消失的是浮动窗口装饰、候选列表,还是其他界面,需要分别排查。
为什么更换定位标志没有解决
COMPOSITIONFORM 同时包含位置和定位样式:
CFS_POINT:指定组合窗口左上角的位置,允许 IME 调整。CFS_FORCE_POSITION:要求使用指定位置,不允许 IME 调整。CFS_DEFAULT:恢复默认定位方式,允许 IME 在客户区外显示浮动组合窗口。
CFS_FORCE_POSITION 控制的是 IME 能否调整位置,不能保证恢复边框、按钮或其他浮动界面元素。CFS_DEFAULT 也无法同时满足恢复默认浮动显示和严格使用指定坐标的要求。COMPOSITIONFORM 文档
Microsoft 的早期 IME 开发指南将 CFS_POINT 用于把组合窗口放到当前文本行中。你观察到的简化外观与这种用途相符,但具体显示效果仍由输入法实现决定。Microsoft IME 开发指南
保留默认绘制时,先确认定位和消息转交
可以继续使用现有方法,并检查 API 返回值。下面的函数假设 clientPos 已经是接收输入的 hwnd 的客户区坐标:
#include <windows.h>
#include <imm.h>
#pragma comment(lib, "Imm32.lib")
bool UpdateImeCompositionPosition(HWND hwnd, POINT clientPos)
{
HIMC himc = ImmGetContext(hwnd);
if (!himc)
return false;
COMPOSITIONFORM cf{};
cf.dwStyle = CFS_POINT;
cf.ptCurrentPos = clientPos;
const BOOL ok = ImmSetCompositionWindow(himc, &cf);
ImmReleaseContext(hwnd, himc);
return ok != FALSE;
}
接入时注意以下几点:
- 确认
m_caret->GetPosition()返回的坐标系。如果返回屏幕坐标,应先用ScreenToClient转换;如果返回画布内部坐标,应先处理滚动、缩放和画布偏移。 - 在组合开始时设置初始位置,并在组合期间插入点或布局变化时更新位置。如果只在
WM_IME_COMPOSITION中更新,可能错过最初显示时或滚动后的定位。 - 如果仍由系统绘制组合文字,就让相应消息继续进入
DefWindowProc。代码中的break只有在跳出switch后确实调用默认窗口过程时才满足这一点;如果后面直接return 0,默认处理就被截断了。WM_IME_COMPOSITION 文档
ImmSetCompositionWindow 返回非零表示调用成功,返回零表示失败。调用成功并不代表输入法一定会采用你期望的外观。ImmSetCompositionWindow 文档
如果只是组合文字外观不协调,设置字体
采用靠近正文的组合显示后,应给组合窗口设置与编辑区相符的字体。假设 editorFont 是编辑区实际使用的有效 HFONT,可以这样设置:
LOGFONTW lf{};
if (GetObjectW(editorFont, sizeof(lf), &lf) == sizeof(lf))
{
HIMC himc = ImmGetContext(hwnd);
if (himc)
{
const BOOL ok = ImmSetCompositionFontW(himc, &lf);
ImmReleaseContext(hwnd, himc);
// Check ok and record a failure if needed.
}
}
ImmSetCompositionFontW 用于设置组合文字的逻辑字体,可以让文字与编辑区更加协调,但不能设置浮动窗口的边框或按钮。ImmSetCompositionFontW 文档
如果消失的是候选列表,要单独排查
组合窗口显示正在输入或转换的文字,候选窗口显示可选择的候选项,两者是不同的界面。ImmSetCompositionWindow 不能代替候选窗口管理。IME 窗口说明
如果实际问题是候选列表没有显示,应检查:
- 输入是否已经进入应当显示候选列表的状态。
- 是否在其他消息处理中屏蔽了候选窗口显示。
- 是否需要通过
ImmSetCandidateWindow配合CANDIDATEFORM单独提供候选窗口的位置。
另外,你尝试的 WM_IME_CONTROL / IMC_SETCOMPOSITIONWINDOW 命令应发给 IME 窗口。如果代码中的 hwnd 是应用窗口,不能直接把它当作 IME 窗口句柄;可以用 ImmGetDefaultIMEWnd(hwnd) 获取对应的默认 IME 窗口。这里继续使用 ImmSetCompositionWindow 更直接,更换调用入口也不会增加保留原有外观的能力。IMC_SETCOMPOSITIONWINDOW 文档、ImmGetDefaultIMEWnd 文档
需要完全控制外观时,自行绘制组合文字
如果需求包含自定义颜色、下划线、选中区或与画布完全一致的布局,就需要应用自行绘制组合文字。
自行绘制时,可以在 WM_IME_SETCONTEXT 中清除 ISC_SHOWUICOMPOSITIONWINDOW,再将消息传给默认窗口过程:
case WM_IME_SETCONTEXT:
lParam &= ~static_cast<LPARAM>(ISC_SHOWUICOMPOSITIONWINDOW);
return DefWindowProcW(hwnd, message, wParam, lParam);
这段代码只是关闭系统组合窗口。应用还必须通过 ImmGetCompositionStringW 读取组合文字、属性和光标位置,处理 GCS_RESULTSTR 提交结果,并在取消或结束组合时清理临时显示。没有完成这些处理前,不应仅通过隐藏系统窗口来消除外观问题。WM_IME_SETCONTEXT 文档、WM_IME_COMPOSITION 文档
备注:内容仅供参考。
