Windows ListView 的欄位標題預設允許使用者拖曳分隔線調整寬度,但某些業務情境(如固定格式報表、嵌入式介面)需鎖定欄寬。本文深入分析 Header Control 通知訊息機制,示範透過訊息反射攔截 `HDN_BEGINTRACK` 實現欄寬鎖定,並同步禁用調整游標,提供完整可復用的 `CLockableHeaderCtrl` 類別。
ListView 內建的 Header Control(`SysHeader32`)在使用者拖曳分隔線前會送出通知:
因 Unicode/ANSI 版本共存,必須同時處理 `HDN_BEGINTRACKA` 與 `HDN_BEGINTRACKW`,或透過 `WM_NOTIFYFORMAT` 協商版本。最簡單穩健的做法是雙版本皆攔截。
#pragma once
#include
class CLockableHeaderCtrl : public CHeaderCtrl
{
DECLARE_DYNAMIC(CLockableHeaderCtrl)
public:
CLockableHeaderCtrl();
virtual ~CLockableHeaderCtrl();
void LockColumnWidth(BOOL bLock = TRUE) { m_bLocked = bLock; }
BOOL IsLocked() const { return m_bLocked; }
protected:
BOOL m_bLocked = FALSE;
DECLARE_MESSAGE_MAP()
// 訊息反射:攔截 HDN_BEGINTRACK
virtual BOOL OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult);
// 禁用拖曳游標
virtual BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
};
#include "stdafx.h"
#include "LockableHeaderCtrl.h"
IMPLEMENT_DYNAMIC(CLockableHeaderCtrl, CHeaderCtrl)
CLockableHeaderCtrl::CLockableHeaderCtrl() {}
CLockableHeaderCtrl::~CLockableHeaderCtrl() {}
BEGIN_MESSAGE_MAP(CLockableHeaderCtrl, CHeaderCtrl)
END_MESSAGE_MAP()
// 關鍵:OnChildNotify 為虛擬函數,可直接覆寫處理反射訊息,無需 ON_NOTIFY_REFLECT 宏
BOOL CLockableHeaderCtrl::OnChildNotify(UINT msg, WPARAM wp, LPARAM lp, LRESULT* pResult)
{
NMHDR* pNMHDR = (NMHDR*)lp;
if (pNMHDR && (pNMHDR->code == HDN_BEGINTRACKA || pNMHDR->code == HDN_BEGINTRACKW))
{
if (m_bLocked)
{
*pResult = TRUE; // 返回 TRUE 取消拖曳
return TRUE; // 訊息已處理
}
}
return CHeaderCtrl::OnChildNotify(msg, wp, lp, pResult);
}
// 鎖定時禁用左右雙箭頭游標,保持預設箭頭
BOOL CLockableHeaderCtrl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg)
{
if (m_bLocked)
return TRUE; // 自行處理,不呼叫基類 → 不會換游標
return CHeaderCtrl::OnSetCursor(pWnd, nHitTest, msg);
}
在 `CListView` 或 `CListCtrl` 衍生類別的 `OnCreate` 中:
int CMyListView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CListView::OnCreate(lpCreateStruct) == -1)
return -1;
// Header Control 的控制項 ID 固定為 0
if (!m_wndHeader.SubclassDlgItem(0, this))
return -1;
return 0;
}
成員變數宣告:`CLockableHeaderCtrl m_wndHeader;`
void CMyListView::OnViewLockColumns()
{
m_wndHeader.LockColumnWidth(!m_wndHeader.IsLocked());
}
void CMyListView::OnUpdateViewLockColumns(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_wndHeader.IsLocked());
}
資源檔加入選單項目 `ID_VIEW_LOCKCOLUMNS`,對應上述處理函數。

鎖定後:拖曳分隔線無反應、游標維持箭頭、選單項目打勾。解鎖後恢復正常拖曳調整。
將 `m_bLocked` 改為 `std::vector
利用 MFC 訊息反射機制攔截 `HDN_BEGINTRACK`,僅需十幾行核心代碼即可完美鎖定 ListView 欄寬,並同步處理游標回饋。`CLockableHeaderCtrl` 封裝良好,可直接放入任何專案復用。此技巧同樣適用於 `CTreeCtrl`、`CListCtrl` 等內建 Header 的通用控制項。