首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

VC++ 鎖定 ListView 欄位標題寬度禁止使用者拖曳調整

     編輯:VC 教學

前言

Windows ListView 的欄位標題預設允許使用者拖曳分隔線調整寬度,但某些業務情境(如固定格式報表、嵌入式介面)需鎖定欄寬。本文深入分析 Header Control 通知訊息機制,示範透過訊息反射攔截 `HDN_BEGINTRACK` 實現欄寬鎖定,並同步禁用調整游標,提供完整可復用的 `CLockableHeaderCtrl` 類別。

一、核心原理:Header Control 通知訊息

ListView 內建的 Header Control(`SysHeader32`)在使用者拖曳分隔線前會送出通知:

  • HDN_BEGINTRACK / HDN_BEGINTRACKA / HDN_BEGINTRACKW:開始追蹤拖曳,返回 TRUE 即可取消該次拖曳操作。
  • HDN_TRACK:拖曳中持續通知。
  • HDN_ENDTRACK:結束拖曳。

因 Unicode/ANSI 版本共存,必須同時處理 `HDN_BEGINTRACKA` 與 `HDN_BEGINTRACKW`,或透過 `WM_NOTIFYFORMAT` 協商版本。最簡單穩健的做法是雙版本皆攔截。

二、自訂 Header Control 類別

CLockableHeaderCtrl.h


#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);
};

CLockableHeaderCtrl.cpp


#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);
}

三、在 ListView 中使用子類化替換原生 Header

在 `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 m_vLocked` 或位元遮罩,`OnChildNotify` 透過 `NMHEADER::iItem` 判斷被拖曳的欄位索引,實現逐欄鎖定。

七、結語

利用 MFC 訊息反射機制攔截 `HDN_BEGINTRACK`,僅需十幾行核心代碼即可完美鎖定 ListView 欄寬,並同步處理游標回饋。`CLockableHeaderCtrl` 封裝良好,可直接放入任何專案復用。此技巧同樣適用於 `CTreeCtrl`、`CListCtrl` 等內建 Header 的通用控制項。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved