“介紹一個有Toolbar功能的可重用類 CPopupText”。這篇文章的內容是關於在一個列表框中,如果列表框中數據項的文字長度超過了列表框本身的寬度,則會顯示一個類似ToolTips的彈出提示,將超長的列表框數據項完整地顯示出來(如圖一)。文中給出的 CPopupText 類非常好用,但是該文章提供的例子代碼——ListCtrl運行起來似乎有點問題,就是在單擊提示條下面的列表框數據項時,無法選中這個項目。如果能完善一下就好了......
圖一 顯示彈出式提示
解答:
提出這個問題的朋友很細心。確實不錯,稍微認真一點的人都不難發現這個例子中存在著上面所說的bug。本文將針對這個問題對程序進行修改和完善。
這個例子程序使用了一個特別的類——CListBoxTipHandler,它的作用是截獲發送到列表框的消息。這個類派生於CSubclassWnd,請讀者們注意,CSubclassWnd是個非常有用的類,它易於使用,可重用性極強。在VC知識庫的很多文章和例子代碼中都使用到了這個類(在其它的商業開發中當然也可以利用這個類)。這個類的作用是將截獲的 Windows 消息發送到另外一個窗口。CListBoxTipHandler類還用到了另一個類——CPopupText,這個類的作用是顯示超長的列表框項目文本。下面我們就來分析一下要實現的目標。
當用戶點擊彈出的提示條文本時(如圖一),要想讓 Windows 忽略提示文本的存在,讓鼠標單擊事件穿透文本直接傳到下面的列表框是行不通的。那麼如何讓才能讓鼠標單擊事件傳到列表框呢?Windows 自有其絕招。
當用戶在屏幕的某個地方點擊鼠標時,Windows 通過其內部機制來決定光標下面是什麼東西,在發送WM_LBUTTONDOWN消息之前,Windows 首先要發送WM_NCHITTEST消息來查詢光標處於哪個非客戶區上方。如果光標在標題上方,則 Windows 返回HTCAPTION。如果光標處於菜單上方,則 Windows 返回HTMENU。如果光標落在客戶區,則 Windows 返回HTCLIENT。大多數應用程序都不處理WM_NCHITTEST消息——一般這個消息都是由缺省的窗口過程(DefWindowProc)處理,所以可能有些人從來就沒有聽說過有這麼一個Windows消息。DefWindowProc 進行所有相應的計算來確定像素是否落在標題,菜單,邊界,大小調整客戶區等區域,同時返回相應的HT碼。在這些返回的HT碼中有一個HTTRANSPARENT。這個返回碼是我們分場感興趣的東西。它告訴Windows,“我是透明的,不要把任何鼠標事件消息發給我,把它們發給下一個窗口吧。”這裡所說的下一個窗口,指的是光標下面Z-坐標上的窗口線程。實際上這就是我們所需要的東西——對CPopupText類做如下修改:
UINT CPopupText::OnNcHitTest(CPoint pt) { return HTTRANSPARENT; }
哈哈,就這麼簡單,bug排除了!現在當用戶單擊如圖一中的提示文本時,它下面的列表框被選中。真神!我喜歡這樣用只有一行代碼的函數就能搞掂的bug。它證明了最初的設計並不令人失望,盡管它不是那麼完美。
但是出現了另外一個問題,當用戶點擊文本時,程序沒有任何響應。列表框的數據項是被選中了,但用戶得不到這個消息和信息。怎麼辦呢?這裡告訴你一個用於界面設計的頭號基本准則:即無論發生什麼事情,都要讓用戶知道......
這個時候點擊鼠標是非常令人不安的,因為想知道到底是選中了還是沒有選中?為了給用戶一些反饋,只需要在CListBoxTipHandler::WindowProc中添加幾行代碼即可,這個函數截獲發送到列表框的消息。
LRESULT CListBoxTipHandler::WindowProc(...) { if (msg==WM_LBUTTONDOWN) { g_wndTip.Cancel(); // 清楚提示文本 break; } return CSubclassWnd::WindowProc(msg, wp, lp); }
現在當用戶選中列表框數據項時,CListBoxTipHandler截獲單擊消息並進行處理,清除彈出得提示文本(如果有的話),並顯示新選中的數據項,如圖二所:
圖二 選中項目後,提示消失
到此這個bug被完全排除......
原來CPopupText類使用的是缺省的狀態字體(NONCLIENTMETRICS::lfStatusFont)。如果使用與列表框同樣的字體效果會更好些,所以我在這方面也進行了改進。
本文示例代碼或素材下載