程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> VC中鎖定ListView的欄目頭寬度

VC中鎖定ListView的欄目頭寬度

編輯:關於VC++

世界之大,真是無其不有。Windows 應用程序的GUI標准明確規定了 ListView 欄目頭(Column Header)的寬度必須是可調整的,這本來是專門為用戶考慮而設計的控制特性,可是偏偏就有用戶拒絕這樣的特性。作為技術人員,用戶的需求是很難拒絕的。 盡管這明顯是一種“非典型性需求”。本文將通過一個實例來示范如何實現 ListView Column Header 寬度的鎖定。

ListView 及其 Column Header 實際上都是 Windows 通用控件(Comctl32.dll) 的一部分。所以查一查 MSDN 中與“Header Control”相關的控件資料不難發現,欄目頭的鎖定與否與幾個 Windows 的通知消息密切相關,這幾個消息分別是 HDN_TRACK、HDN_BEGINTRACK 和 HDN_ENDTRACKA。其中 HDN_BEGINTRACK 是本文要特別關照的一個。當用戶在欄目頭上拖拽鼠標時,如果位置正好在改變寬度的分割條上,則欄目頭控件會向其父窗口發送一個 HDN_BEGINTRACK 通知消息。為了實現欄目頭寬度的鎖定,就必須搞掂這個通知消息。不能將它傳遞到父窗口,但是,這個消息與 Windows 中形形色色的其它通知消息一樣,有兩個版本:一個版本是 HDN_BEGINTRACKW,專門用於寬字符和 Unicode 字符集;另一個版本是 HDN_BEGINTRACKA,專門用於 ANSI 字符集。這兩個版本的使用方法可以從公共控件的頭文件 commctrl.h 中獲取:

// From commctrl.h
#ifdef UNICODE
#define HDN_BEGINTRACK HDN_BEGINTRACKW
#else
#define HDN_BEGINTRACK HDN_BEGINTRACKA
#endif  

所以在實現對消息的 HDN_BEGINTRACK 處理時,實際上是根據 UNICODE 的取值實現對 HDN_BEGINTRACKA 或 HDN_BEGINTRACKW 的處理。那麼 Header Control 到底是發送的哪一個消息呢?在這裡必須明白:Header Control 是 Windows 通用控件的一部分,它的實現都在 comctl32.dll 動態鏈接庫中。由於這個 DLL 已經被編譯成可執行代碼,因此在工程中修改 UNICODE 的設置將無濟於事。如何知道欄目頭控件發送哪一個版本的通知消息呢?是 A 版本還是 W 版本?

為了找到答案,我們必須求助一個經常被遺忘的消息 WM_NOTIFYFORMAT。一般控件第一次被創建時,都要向父窗口一個消息詢問父窗口需要哪個版本的通知消息。然後父窗口返回 NFR_ANSI 或 NFR_UNICODE。如果父窗口不處理 WM_NOTIFYFORMAT,那麼這個消息將根據父窗口或對話框本身的首選項被傳遞到 Windows 的 DefWindowProc 消息處理例程進行默認處理。默認為 UNICODE。因此,要知道通知消息的版本,必須處理 ListCtrl 的 WM_NOTIFYFORMAT。為了確認父窗口的返回值,你可以做一個試驗便明白了。

如果你不想處理 WM_NOTIFYFORMAT 消息,那麼完全可以通過雙雙實現 HDN_BEGINTRACKA 和 HDN_BEGINTRACKW 通知消息的處理來簡化問題的解決方案,同時這種方法也更可靠和通用。此時代碼將同時支持 ANSI 和 Unicode。本文附帶的例子程序示范了這種方法的實現。如圖一所示:

圖一 鎖定欄目頭寬度

實現代碼很簡單,Header 控件發送 HDN_XXX 到父窗口(ListCtrl),在 MFC 中可以利用消息反射來處理 Header 控件的通知消息。因為“可鎖定欄目頭”特性本身更趨向於 Header 控件的屬性,而不是 ListCtrl 的屬性。如果你不用 MFC ,那麼就得處理 ListCtrl 中的通知消息。例子程序使用了消息反射機制,在 Header 控件的消息映射使用 ON_NOTIFY_REFLECT,也就是該寫虛擬成員函數 OnChildNotify:

BOOL CLockableHeader::OnChildNotify(UINT msg, WPARAM wp, LPARAM lp, LRESULT* pRes)
{
   NMHDR& nmh = *(NMHDR*)lp;
   if (nmh.code==HDN_BEGINTRACKW || nmg.code==HDN_BEGINTRACKA)
   return *pRes=TRUE;
   ......
}

因為 OnChildNotify 是虛函數,所以沒有必要具備消息映射入口。只要實現此函數即可。在任何應用中,Header 發送的消息非此即彼,不會兩者都發送。不管怎樣,所發送的通知消息在到達父窗口之前都會被吃掉。也就是說,消息處理總是返回 TRUE,是否鎖定欄目頭的寬度通過一個標志來控制:應用程序通過 Lock 來修改標志的值。

如果鎖定了頭寬度,那麼同時也必須禁用改變寬度的光標,這樣用戶界面才會有一致性,要實現這一點也很簡單:

BOOL CLockableHeader::OnSetCursor( CWnd* pWnd, UINT nHit, UINT msg)
{
   return m_bLocked ? TRUE : CHeaderCtrl::OnSetCursor(pWnd, nHit, msg);
}   

如果欄目頭被鎖定,則 OnSetCursor 返回 TRUE,此時光標不會被重新設置,否則由 Header 控件的進行默認處理。鎖定寬度後,當鼠標移到欄目頭上時,Windows 顯示標准的箭頭光標,而不是帶左右箭頭光標。

從 CHeaderCtrl 派生類出來的類的使用方法與處理對話框控制一樣,通過在父窗口的 OnCreate 的處理例程中進行子類化。實現細節請參考例子源代碼:

// CMyView is derived from CListView
int CMyView::OnCreate(LPCREATESTRUCT lpcs)
{
  VERIFY(CListView::OnCreate(lpcs)==0);
  return m_header.SubclassDlgItem(0,this) ? 0 : -1;
}

由於 Header 控制的資源 ID = 0,所以上面的代碼是行得通的。為了有一個友好的用戶界面,例子程序創建了一個命令菜單和界面更新處理例程。如圖一所示。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved