模擬「回收站」功能:用戶選中一個樹節點(含其所有子孫節點),點擊「刪除」按鈕,將整個子樹移動到「回收站」節點下,保持層級結構不變,支持後續還原/徹底刪除。
MFC `CTreeCtrl` 無內置「移動子樹」方法,需手動實現:
1. 在目標父節點下創建新節點(複製文本、圖標、數據)。
2. 遞歸遍歷源節點所有子節點,重複步驟 1。
3. 遞歸完成後,刪除源節點(會自動刪除其所有子孫)。
// 將 hSrcItem 及其所有子孫遞歸複製到 hDstParent 下
HTREEITEM CopySubTree(CTreeCtrl& tree, HTREEITEM hSrcItem, HTREEITEM hDstParent) {
if (!hSrcItem) return NULL;
// 1. 複製當前節點屬性
CString strText = tree.GetItemText(hSrcItem);
int nImage = 0, nSelectedImage = 0;
tree.GetItemImage(hSrcItem, nImage, nSelectedImage);
DWORD_PTR dwData = tree.GetItemData(hSrcItem);
// 2. 在目標父節點下插入新節點
HTREEITEM hNewItem = tree.InsertItem(strText, nImage, nSelectedImage, hDstParent, TVI_LAST);
tree.SetItemData(hNewItem, dwData); // 保持用戶數據
// 3. 遞歸處理子節點
HTREEITEM hChild = tree.GetChildItem(hSrcItem);
while (hChild) {
CopySubTree(tree, hChild, hNewItem);
hChild = tree.GetNextSiblingItem(hChild);
}
return hNewItem;
}
void CMyTreeView::OnMoveToRecycleBin() {
HTREEITEM hSel = m_tree.GetSelectedItem();
if (!hSel) return;
HTREEITEM hRecycle = FindRecycleBinItem(); // 查找或創建「回收站」根節點
if (!hRecycle) return;
// 防止將回收站自己移動進自己
if (hSel == hRecycle || IsChildOf(hSel, hRecycle)) {
AfxMessageBox(_T("不能將回收站移動到自身內部"));
return;
}
// 1. 複製子樹到回收站
HTREEITEM hNewRoot = CopySubTree(m_tree, hSel, hRecycle);
// 2. 刪除原節點(含所有子孫)
m_tree.DeleteItem(hSel);
// 3. 展開回收站顯示
m_tree.Expand(hRecycle, TVE_EXPAND);
m_tree.SelectItem(hNewRoot);
}
HTREEITEM CMyTreeView::FindRecycleBinItem() {
HTREEITEM hRoot = m_tree.GetRootItem();
HTREEITEM hItem = m_tree.GetChildItem(hRoot);
while (hItem) {
if (m_tree.GetItemText(hItem) == _T("回收站")) return hItem;
hItem = m_tree.GetNextSiblingItem(hItem);
}
// 不存在則創建
return m_tree.InsertItem(_T("回收站"), 2, 2, hRoot, TVI_LAST); // 圖標索引 2
}
BOOL CMyTreeView::IsChildOf(HTREEITEM hItem, HTREEITEM hAncestor) {
while (hItem) {
hItem = m_tree.GetParentItem(hItem);
if (hItem == hAncestor) return TRUE;
}
return FALSE;
}
| 要點 | 說明 |
|---|---|
| 項目數據 | 用 `SetItemData` 保存指針/ID,複製時同步,業務邏輯不中斷 |
| 圖標狀態 | 複製 `nImage`/`nSelectedImage`,保持展開/折疊視覺一致 |
| 性能優化 | 大量節點時用 `SetRedraw(FALSE)` 禁用重繪,操作完 `SetRedraw(TRUE); Invalidate();` |
| 撤銷支持 | 記錄「源父節點、源位置、目標節點」,實現 Undo 需反向移動 |
| 拖拽移動 | 同理,`TVN_BEGINDRAG`/`TVN_ENDDRAG` 中計算目標,調用 `CopySubTree` + `DeleteItem` |
MFC `CTreeCtrl` 移動子樹的本質是 「遞歸複製 + 刪除源」。封裝通用 `CopySubTree` 函數,配合項目數據、圖標、防循環檢查,即可構建健壯的「回收站」、「拖拽排序」、「複製分支」等高級樹操作功能。