VC 知識庫經典文章《擁有 Toolbar 功能的可重用類 CPopupText》提供了一個實用的方案:當列表框項目文字過長超出寬度時,滑鼠懸停顯示完整文字提示。然而,原版範例存在一個嚴重 Bug —— 點擊提示文字區域時,無法選中下方的列表框項目。本文將深入分析原因,並提供徹底修復方案,同時優化字體渲染體驗。
原架構中,CListBoxTipHandler (繼承自 CSubclassWnd) 子類化列表框,攔截 WM_MOUSEMOVE 判斷是否顯示 CPopupText 提示視窗。但當提示視窗覆蓋在列表項上方時,它攔截了滑鼠訊息,導致底層列表框收不到 WM_LBUTTONDOWN。

圖一:彈出式提示遮擋列表項
Windows 訊息機制中,WM_NCHITTEST 是關鍵。當滑標在視窗上方時,系統先發送此訊息詢問「這裡屬於哪個非客戶區?」。若視窗程序返回 HTTRANSPARENT,系統會將該視窗視為「透明」,將滑鼠訊息傳遞給 Z 序下方的視窗。只要在 CPopupText 中重寫此虛函數即可:
UINT CPopupText::OnNcHitTest(CPoint point)
{
return HTTRANSPARENT; // 告訴系統:我是透明的,請把點擊訊息傳給下一層視窗
}
神奇地,列表框現在能正常接收點擊並選中項目了!
雖然點擊能穿透了,但提示視窗瞬間消失,使用者可能不確定是否真的點中。最佳體驗是:點擊瞬間清除提示,並高亮顯示新選中的項目。在 CListBoxTipHandler::WindowProc 中攔截 WM_LBUTTONDOWN:
LRESULT CListBoxTipHandler::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
if (msg == WM_LBUTTONDOWN)
{
g_wndTip.Cancel(); // 立即清除提示文字
// 此處不需 break,讓訊息繼續傳遞給列表框處理選中邏輯
}
return CSubclassWnd::WindowProc(msg, wp, lp);
}

圖二:點選後提示消失,項目被正確選中
原版 CPopupText 使用系統狀態列字體 (NONCLIENTMETRICS::lfStatusFont),與列表框字體常不一致。建議在建立視窗時,明確設定與列表框相同的字體 (SetFont(pListBox->GetFont())),視覺體驗更專業統一。
修復核心僅需兩處微調:HTTRANSPARENT 解決穿透,WM_LBUTTONDOWN 處理回饋。這證明了原設計架構的優良擴展性。完善後的 CPopupText 現已可安心用於生產環境的 ListBox、ComboBox、ListView 等控制項。