Windows 檔案總管左側的樹狀結構是 TreeView 控制項的經典應用。Delphi 內建的 TTreeView 雖支援節點圖示,但無法直接內嵌 CheckBox 或 RadioButton。本文介紹一種「以圖示模擬」的輕量級方案:利用 StateImages 屬性 切換「勾選/未勾選」圖示,配合滑鼠鍵盤事件,實現直觀的節點選取功能。

相比繼承重寫組件的高複雜度,利用 TreeView 內建的 StateImages 機制:
StateIndex 屬性指向 StateImages 中的圖示索引StateIndex 即可視覺呈現狀態變化
0: 複選框未勾選、1: 複選框勾選、2: 單選鈕未選、3: 單選鈕選中StateImages := ImageList1// 狀態索引常數(對應 ImageList 索引)
const
cFlatUnCheck = 0; // 複選框未勾選
cFlatChecked = 1; // 複選框勾選
cFlatRadioUnCheck = 2; // 單選鈕未選
cFlatRadioChecked = 3; // 單選鈕選中
procedure ToggleTreeViewCheckBoxes(Node: TTreeNode;
cUnChecked, cChecked, cRadioUnchecked, cRadioChecked: Integer);
var
tmp: TTreeNode;
begin
if not Assigned(Node) then Exit;
// 複選框模式:單純切換
if Node.StateIndex = cUnChecked then
Node.StateIndex := cChecked
else if Node.StateIndex = cChecked then
Node.StateIndex := cUnChecked
// 單選鈕模式:同層級互斥
else if Node.StateIndex = cRadioUnchecked then begin
tmp := Node.Parent;
if not Assigned(tmp) then
tmp := TTreeView(Node.TreeView).Items.GetFirstNode
else
tmp := tmp.GetFirstChild;
while Assigned(tmp) do begin
if tmp.StateIndex in [cRadioUnchecked, cRadioChecked] then
tmp.StateIndex := cRadioUnchecked;
tmp := tmp.GetNextSibling;
end;
Node.StateIndex := cRadioChecked;
end;
end;
滑鼠點擊節點狀態圖示區域時:
procedure TForm1.TreeView1Click(Sender: TObject);
var
P: TPoint;
begin
GetCursorPos(P);
P := TreeView1.ScreenToClient(P);
// 判斷點擊位置是否在 StateIcon 區域
if htOnStateIcon in TreeView1.GetHitTestInfoAt(P.X, P.Y) then
ToggleTreeViewCheckBoxes(TreeView1.Selected,
cFlatUnCheck, cFlatChecked, cFlatRadioUnCheck, cFlatRadioChecked);
end;
鍵盤空白鍵切換選中節點:
procedure TForm1.TreeView1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_SPACE) and Assigned(TreeView1.Selected) then
ToggleTreeViewCheckBoxes(TreeView1.Selected,
cFlatUnCheck, cFlatChecked, cFlatRadioUnCheck, cFlatRadioChecked);
end;
放置 Button 與 Memo,點擊按鈕顯示目前節點狀態:
procedure TForm1.Button1Click(Sender: TObject);
var
BoolResult: Boolean;
tn: TTreeNode;
begin
if Assigned(TreeView1.Selected) then begin
tn := TreeView1.Selected;
BoolResult := tn.StateIndex in [cFlatChecked, cFlatRadioChecked];
Memo1.Text := tn.Text + #13#10 + 'Selected: ' + BoolToStr(BoolResult, True);
end;
end;

| 優點 | 缺點/限制 |
|---|---|
| 零組件繼承,幾行代碼即可完成 | 非真實 CheckBox,無法直接綁定資料感知組件 |
| 效能極佳,無額外視窗控制代碼開銷 | 需自行維護狀態邏輯(如父子節點聯動) |
| 視覺風格完全可控(自訂圖示) | 無法響應螢幕閱讀器等無障礙 API |
進階擴充方向:
OnCustomDrawItem 繪製更豐富的節點樣式
這種「以圖示模擬互動控制項」的技巧,同樣適用於 ListView、ListBox 等支援 StateImages 的組件,是 Delphi 開發者實用的 UI 進階技法。