TreeView(樹狀檢視)是呈現階層式資料的核心控件,廣泛應用於檔案總管、註冊表編輯器等經典介面。本文詳細說明 TreeView 的建立、樣式設定、影像清單關聯、項目插入與擷取、通知訊息處理,並完整實作拖放(Drag-Drop)操作流程,附 MASM 範例碼。
透過 CreateWindowEx 指定類別名 SysTreeView32 建立,必須先呼叫 InitCommonControls。常用視窗樣式:
TVS_HASBUTTONS:父項目顯示 +/- 按鈕,點擊展開/摺疊TVS_HASLINES:項目間以連線顯示階層關係TVS_LINESATROOT:根節點亦顯示連線(需配合 TVS_HASLINES)範例組合:WS_CHILD|WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT
TreeView 項目左側圖示來源於影像清單:
invoke ImageList_Create, 16, 16, ILC_COLOR16, 2, 10 ; 16x16, 16色, 初始2張, 最大10張
mov hImageList, eax
invoke LoadBitmap, hInstance, IDB_TREE
invoke ImageList_Add, hImageList, eax, NULL
invoke DeleteObject, eax ; 釋放位圖控制碼
invoke SendMessage, hwndTreeView, TVM_SETIMAGELIST, TVSIL_NORMAL, hImageList
TVSIL_NORMAL 含一般/選取兩種狀態圖示;TVSIL_STATE 供自訂狀態使用。
核心結構:
TV_INSERTSTRUCT:
hParent DWORD ? ; 父項目控制碼,NULL/TVI_ROOT 表根節點
hInsertAfter DWORD ? ; TVI_FIRST/TVI_LAST/TVI_SORT
item TVITEM <> ; 項目屬性
TVITEM:
imask DWORD ? ; 有效欄位旗標:TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE...
hItem DWORD ? ; 項目控制碼(由系統指派)
state DWORD ?
stateMask DWORD ?
pszText DWORD ? ; 項目標籤字串指標
cchTextMax DWORD ? ; 緩衝區大小(查詢時用)
iImage DWORD ? ; 未選取時圖示索引
iSelectedImage DWORD ? ; 選取時圖示索引
cChildren DWORD ?
lParam DWORD ? ; 應用程式自訂資料
插入根節點範例:
mov tvinsert.hParent, NULL
mov tvinsert.hInsertAfter, TVI_ROOT
mov tvinsert.item.imask, TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
mov tvinsert.item.pszText, offset Parent
mov tvinsert.item.iImage, 0
mov tvinsert.item.iSelectedImage, 1
invoke SendMessage, hwndTreeView, TVM_INSERTITEM, 0, addr tvinsert
mov hParent, eax ; 取得根節點控制碼
子節點插入時 hParent = hParent,hInsertAfter = TVI_LAST。
若無項目控制碼,可用 TVM_GETNEXTITEM 依關係取得:
TVGN_CARET:目前選取項目TVGN_CHILD:第一個子項目TVGN_PARENT:父項目TVGN_NEXT / TVGN_PREVIOUS:同層下一個/上一個TVGN_ROOT:根項目TVGN_DROPHILITE:拖放目標高亮項目取得 hItem 後,填入 TVITEM.hItem 並設定 imask,發送 TVM_GETITEM 即可擷取資訊。
TreeView 拖放非自動處理,需手動協調影像清單與滑鼠捕捉:
.elseif uMsg==WM_NOTIFY
mov edi, lParam
assume edi:ptr NM_TREEVIEW
.if [edi].hdr.code == TVN_BEGINDRAG
invoke SendMessage, hwndTreeView, TVM_CREATEDRAGIMAGE, 0, [edi].itemNew.hItem
mov hDragImageList, eax
invoke ImageList_BeginDrag, hDragImageList, 0, 0, 0 ; 熱點設左上角
invoke ImageList_DragEnter, hwndTreeView, [edi].ptDrag.x, [edi].ptDrag.y
invoke SetCapture, hWnd
mov DragMode, TRUE
.endif
assume edi:nothing
TVM_CREATEDRAGIMAGE 回傳含單一拖放圖示的影像清單控制碼。
.elseif uMsg==WM_MOUSEMOVE
.if DragMode==TRUE
; 解析 lParam 取得 x, y
mov eax, lParam
and eax, 0FFFFh
mov ecx, lParam
shr ecx, 16
mov tvhit.pt.x, eax
mov tvhit.pt.y, ecx
invoke ImageList_DragMove, eax, ecx
invoke ImageList_DragShowNolock, FALSE ; 隱藏拖放圖示以便 HitTest
invoke SendMessage, hwndTreeView, TVM_HITTEST, 0, addr tvhit
.if eax != NULL
invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_DROPHILITE, eax
.endif
invoke ImageList_DragShowNolock, TRUE ; 恢復顯示
.endif
關鍵技巧:ImageList_DragShowNolock(FALSE) 暫時隱藏拖放圖示,避免干擾 TVM_HITTEST 判斷滑鼠下方項目;高亮顯示用 TVGN_DROPHILITE。
.elseif uMsg==WM_LBUTTONUP
.if DragMode==TRUE
invoke ImageList_DragLeave, hwndTreeView
invoke ImageList_EndDrag
invoke ImageList_Destroy, hDragImageList
invoke SendMessage, hwndTreeView, TVM_GETNEXTITEM, TVGN_DROPHILITE, 0
invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_CARET, eax ; 正式選取
invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_DROPHILITE, 0 ; 移除高亮
invoke ReleaseCapture
mov DragMode, FALSE
.endif
必須清理拖放狀態:銷毀拖放影像清單、釋放滑鼠捕捉、將高亮項目轉為正常選取狀態,否則後續選取會出現視覺殘留。
程式包含標準視窗類別註冊、WM_CREATE 建立 TreeView 與影像清單、插入根/子節點、WM_NOTIFY 處理 TVN_BEGINDRAG、WM_MOUSEMOVE/WM_LBUTTONUP 實作拖放邏輯、WM_DESTROY 清理。完整 MASM 碼見原文。
TreeView 是功能豐富但需手工處理細節的控件。掌握 TV_INSERTSTRUCT 項目插入、TVM_GETNEXTITEM 導航、影像清單關聯,以及拖放流程中的 TVM_CREATEDRAGIMAGE/ImageList_Drag* 協作機制,即可構建專業級階層式介面。