WinForm 控制項預設無法在空白表單啟用輸入法(IME)。本文展示如何透過 P/Invoke 呼叫 Imm32.dll 關聯輸入法上下文,攔截 WM_IME_CHAR 訊息取得組合字串,並解決重複字符 Bug,打造類 Photoshop 文字工具的輸入體驗。
Form/UserControl 不接收 IME 輸入,設定 ImeMode 無效。HIMC (Input Method Context)。WM_IME_CHAR 會觸發兩次,導致重複字符(如「為」→「為為」)。
[DllImport("imm32.dll")]
public static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("imm32.dll")]
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll")]
static extern int ImmGetCompositionString(IntPtr hIMC, int dwIndex, StringBuilder lpBuf, int dwBufLen);
const int WM_IME_SETCONTEXT = 0x0281;
const int WM_IME_CHAR = 0x0286;
const int WM_CHAR = 0x0102; // 英數字元
const int WM_IME_COMPOSITION = 0x010F;
const int GCS_COMPSTR = 0x0008; // 組合字串
const int GCS_RESULTSTR = 0x0800; // 結果字串
const int PM_REMOVE = 0x0001; // 訊息移除標記,用於去重
private IntPtr m_hImc;
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
m_hImc = ImmGetContext(this.Handle); // 取得預設 IMC
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
// 視窗獲得焦點時,關聯 IMC 啟用輸入法
if (m.Msg == WM_IME_SETCONTEXT && m.WParam.ToInt32() == 1) {
ImmAssociateContext(this.Handle, m_hImc);
}
switch (m.Msg) {
case WM_CHAR: // 英數直接輸入
char c = (char)m.WParam.ToInt32();
sb.Append(c);
DrawText();
isChinese = false;
break;
case WM_IME_CHAR: // 中文組合字串
// 關鍵去重:僅處理 PM_REMOVE 狀態
if (m.WParam.ToInt32() == PM_REMOVE) {
StringBuilder str = new StringBuilder();
int size = ImmGetCompositionString(m_hImc, GCS_COMPSTR, null, 0);
size += sizeof(char);
ImmGetCompositionString(m_hImc, GCS_RESULTSTR, str, size);
sb.Append(str.ToString());
DrawText();
isChinese = true;
}
break;
}
}
private StringBuilder sb = new StringBuilder();
private Font font = new Font("新細明體", 14, FontStyle.Regular);
private void DrawText() {
using (Graphics g = this.CreateGraphics()) {
g.Clear(BackColor);
g.DrawString(sb.ToString(), font, Brushes.Black, 10, 10);
}
}
原因:WM_IME_CHAR 在組合過程中會發送兩次(一次對應 GCS_COMPSTR 組合中,一次對應 GCS_RESULTSTR 確定後),若無條件處理會導致字符疊加。
解決:檢查 m.WParam == PM_REMOVE (0x0001),僅在訊息從佇列移除時處理一次,完美消除重複。
public partial class ChineseInputControl : UserControl {
IntPtr m_hImc;
bool isChinese = false;
StringBuilder sb = new StringBuilder();
Font font = new Font("新細明體", 14);
// ... OnLoad, WndProc, DrawText 如上
}
WM_IME_COMPOSITION 處理組合過程中的預覽顯示(底線標示)。ImmSetCompositionWindow 設定候選視窗位置跟隨游標。Text 屬性雙向繫結。掌握 Imm32 互操作,即可在任何 WinForm 繪圖面、遊戲介面、自訂編輯器中實現原生級中文輸入體驗。