前言
編輯控制項是 Windows 應用程式中最常見的文字輸入元件。掌握其通知訊息 (控制項→父視窗) 與控制訊息 (父視窗→控制項) 兩大類訊息,即可實作輸入驗證、自動完成、語法高亮、撤銷/重做、密碼遮罩、唯讀切換等進階功能。
一、通知訊息 (WM_COMMAND / WM_NOTIFY 透傳)
父視窗透過 WM_COMMAND 接收,HIWORD(wParam) 為通知碼,LOWORD(wParam) 為控制項 ID,lParam 為控制項 HWND。
| 通知碼 | 觸發時機 | 典型應用 |
| EN_CHANGE | 文字內容實際改變後 | 即時字數統計、儲存按鈕啟用、自動存檔觸發 |
| EN_UPDATE | 文字即將更新 (重繪前) | 語法高亮預處理、輸入法組合字串攔截 |
| EN_SETFOCUS / EN_KILLFOCUS | 獲得/失去輸入焦點 | 顯示/隱藏工具提示、驗證整體輸入、啟用輸入法模式 |
| EN_MAXTEXT | 嘗試超過文字長度限制 (EM_LIMITTEXT/MaxLength) | 彈出警告、自動截斷提示、記錄截斷事件 |
| EN_HSCROLL / EN_VSCROLL | 使用者點擊水平/垂直捲軸 | 同步外部捲軸、記錄捲動位置、延遲載入大檔案區塊 |
| EN_ERRSPACE | 記憶體不足無法完成編輯操作 | 釋放緩衝、提示關閉其他程式、降低編輯功能 |
二、控制訊息 (SendMessage 發送)
1. 撤銷/重做系統
| 訊息 | 用途 | 回傳值 |
| EM_CANUNDO | 查詢是否可撤銷 | TRUE/FALSE |
| EM_UNDO | 執行撤銷 | TRUE 成功 |
| EM_EMPTYUNDOBUFFER | 清除撤銷緩衝 (不可復原) | 無 |
2. 文字長度與限制
| 訊息 | 用途 | 參數 |
| EM_LIMITTEXT / EM_SETLIMITTEXT | 設定最大文字長度 (字元數) | wParam = max chars (0 = 64KB 預設) |
| EM_GETLIMITTEXT | 取得目前限制 | 回傳上限值 |
3. 選取範圍操作
| 訊息 | 用途 | 參數說明 |
| EM_GETSEL | 取得選取範圍 | wParam=起始指標, lParam=結束指標 (以字元索引) |
| EM_SETSEL | 設定選取範圍 | wParam=start, lParam=end; -1 代表游標位置/結尾 |
| EM_REPLACESEL | 以指定文字取代選取範圍 | lParam=LPCTSTR 新文字; 可用於插入/追加/清空 |
4. 行與座標轉換 (多行編輯專用)
| 訊息 | 用途 | 關鍵參數/回傳 |
| EM_GETLINECOUNT | 取得總行數 | 回傳 int |
| EM_GETLINE | 複製指定行內容至緩衝區 | wParam=行索引(0-based), lParam=緩衝區指標 (首字為長度) |
| EM_LINEINDEX | 取得指定行第一個字元索引 | wParam=行索引, 回傳字元索引 |
| EM_LINELENGTH | 取得指定行長度 | wParam=字元索引, 回傳該行長度 |
| EM_LINEFROMCHAR | 字元索引對應行號 | wParam=字元索引, 回傳行號 |
| EM_POSFROMCHAR / EM_CHARFROMPOS | 字元索引 ↔ 用戶區座標 (POINT) | lParam=字元索引 / 回傳 MAKELONG(x,y) |
| EM_GETFIRSTVISIBLELINE | 取得目前可見區域首行索引 | 回傳行號 |
| EM_LINESCROLL | 垂直/水平捲動行數/字元數 | wParam=水平字元, lParam=垂直行數 |
5. 格式化與製表位
| 訊息 | 用途 |
| EM_FMTLINES | 取得文字時是否包含軟換行字元 (wParam=TRUE/FALSE) |
| EM_SETTABSTOPS | 設定製表位間距 (以對話單位),wParam=數量, lParam=陣列指標 |
| EM_SETMARGINS / EM_GETMARGINS | 設定/取得左右邊距 (EC_LEFTMARGIN/EC_RIGHTMARGIN/EC_USEFONTINFO) |
| EM_SETRECT / EM_SETRECTNP / EM_GETRECT | 設定/取得格式化矩形 (NP=不重繪) |
6. 密碼與唯讀模式
| 訊息 | 用途 |
| EM_SETPASSWORDCHAR | 設定密碼遮罩字元 (wParam=字元, 0=關閉) |
| EM_GETPASSWORDCHAR | 取得目前遮罩字元 |
| EM_READONLY | 設定/取消唯讀狀態 (wParam=TRUE/FALSE) |
| EM_SETMODIFY / EM_GETMODIFY | 設定/查詢修改旗標 (內容是否異動) |
三、進階應用範例
即時字數統計與長度限制提示
// 父視窗 WM_COMMAND 處理
case EN_CHANGE:
int len = SendMessage(hEdit, WM_GETTEXTLENGTH, 0, 0);
SetDlgItemInt(hDlg, IDC_STAT_COUNT, len, FALSE);
if (len >= MAX_LEN) {
// 可選:自動截斷或禁用輸入
SendMessage(hEdit, EM_SETSEL, MAX_LEN, -1);
SendMessage(hEdit, EM_REPLACESEL, 0, (LPARAM)L"");
}
break;
程式化插入時間戳記
void InsertTimestamp(HWND hEdit) {
WCHAR buf[64];
SYSTEMTIME st; GetLocalTime(&st);
swprintf(buf, L"[%04d-%02d-%02d %02d:%02d:%02d] ",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
SendMessage(hEdit, EM_REPLACESEL, TRUE, (LPARAM)buf);
}
同步捲軸位置 (雙編輯視窗比對)
case EN_VSCROLL:
int firstLine = SendMessage(hEdit1, EM_GETFIRSTVISIBLELINE, 0, 0);
SendMessage(hEdit2, EM_LINESCROLL, 0, firstLine -
SendMessage(hEdit2, EM_GETFIRSTVISIBLELINE, 0, 0));
break;
四、Unicode 與 DPI 注意事項
- 所有文字訊息 (
WM_SETTEXT, EM_REPLACESEL, EM_GETLINE) 在 Unicode 專案中接受 wchar_t*,ANSI 專案接受 char*;建議統一使用 Unicode 建置。
- High-DPI 環境下,
EM_POSFROMCHAR 回傳的座標為邏輯像素 (非物理像素),搭配 GetDpiForWindow 換算。
EM_SETTABSTOPS 的單位為對話單位 (1/4 字元寬 × 1/8 字元高),需以 MapDialogRect 轉換為像素。
結語
編輯控制項訊息集涵蓋從基本輸入到專業文字編輯器所需的所有底層操作。善用 EM_GETSEL/SETSEL/REPLACESEL 三劍客即可實作 90% 的文字操作邏輯;多行編輯則需熟練行/字元/座標轉換 API。現代開發若需語法高亮、代碼摺疊、巨集記錄等進階功能,建議改用 Scintilla (SciLexer.dll) 或 RichEdit 4.1+ (msftedit.dll) 控制項,它們繼承並大幅擴充了標準編輯控制項的訊息介面。