Delphi 的 TListView 在報表模式下,使用者可拖曳欄標分隔線調整寬度。若需禁止調整、記錄變更或觸發自定義邏輯,需透過子類化攔截 Header 控件的 WM_NOTIFY 訊息。本文展示完整實現方案。
SysHeader32)管理欄標HDN_BEGINTRACK / HDN_ENDTRACK 通知WindowProc 子類化攔截 WM_NOTIFY,檢查 HD_NOTIFY 結構unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, CommCtrl;
type
PHD_NOTIFY = ^HD_NOTIFY; // 別名簡化類型轉換
TForm1 = class(TForm)
ListView1: TListView;
Button1: TButton;
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
OldProc: TWndMethod;
procedure NewProc(var Message: TMessage);
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.NewProc(var Message: TMessage);
var
hn: PHD_NOTIFY;
begin
if Message.Msg = WM_NOTIFY then
begin
hn := PHD_NOTIFY(Message.LParam);
// 攔截開始拖曳與結束拖曳(ANSI/Unicode 兩套代碼)
if (hn^.hdr.code = HDN_BEGINTRACKA) or (hn^.hdr.code = HDN_BEGINTRACKW) then
begin
// Message.Result := 1; // 取消註釋可「禁止調整欄寬」
ShowMessage('偵測到開始拖曳欄寬'); // 自定義邏輯
end
else if (hn^.hdr.code = HDN_ENDTRACKA) or (hn^.hdr.code = HDN_ENDTRACKW) then
begin
ShowMessage('欄寬調整完成'); // 此處可讀取新寬度並持久化
end
else
ListView1.Dispatch(Message); // 其他訊息轉發給原處理器
end
else
ListView1.Dispatch(Message);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
ListView1.WindowProc := OldProc; // 恢復原始 WindowProc,防止洩漏
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
OldProc := ListView1.WindowProc; // 備份原始 WindowProc
ListView1.WindowProc := NewProc; // 安裝子類化鉤子
end;
end.
type
HD_NOTIFY = record
hdr: TNMHDR; // 標準通知頭:hwndFrom, idFrom, code
iItem: Integer; // 欄索引
iButton: Integer; // 滑鼠按鈕
pitem: PHDITEM; // 指向 HDITEM 結構(含寬度、對齊等屬性)
end;
| 代碼 | 觸發時機 | 典型用途 |
|---|---|---|
| HDN_BEGINTRACKA/W | 開始拖曳分隔線 | 禁止調整、記錄初始寬度 |
| HDN_ENDTRACKA/W | 結束拖曳(放開滑鼠) | 儲存新寬度、觸發重排版 |
| HDN_ITEMCHANGINGA/W | 欄屬性即將變更 | 驗證/修改新屬性 |
| HDN_ITEMCHANGEDA/W | 欄屬性已變更 | 同步 UI 狀態 |
| HDN_DIVIDERDBLCLICKA/W | 雙擊分隔線 | 自動調整欄寬 |
procedure TForm1.NewProc(var Message: TMessage);
var
hn: PHD_NOTIFY;
i: Integer;
begin
if (Message.Msg = WM_NOTIFY) and
((hn^.hdr.code = HDN_ENDTRACKA) or (hn^.hdr.code = HDN_ENDTRACKW)) then
begin
// 遍歷所有欄位,將寬度存入註冊表/INI
for i := 0 to ListView1.Columns.Count - 1 do
WriteIniInt('ListViewWidths', ListView1.Columns[i].Caption, ListView1.Columns[i].Width);
end
else
ListView1.Dispatch(Message);
end;
HDN_BEGINTRACKA / HDN_BEGINTRACKW 視專案字串模式而定,建議同時檢查Message.Result := 1 即可,無需 ShowMessageTListView.OnColumnClick 無法攔截拖曳,但可用 ListView_SetColumnWidth API 事後重置掌握這項子類化技術,不僅能控制 ListView 欄寬行為,同樣適用於 TreeView、HeaderControl 等所有發送 WM_NOTIFY 的 Windows 公共控件。