在動態建立樹狀結構 (如目錄管理、組織架構) 時,新增節點前檢查名稱是否重複是基本需求。MFC CTreeCtrl 未提供內建搜尋方法,本文示範衍生 CTreeCtrlEx 加入 FindItem 遞迴搜尋函式,並搭配 TVN_ENDLABELEDIT 實作「編輯標籤時自動查重」的完整範例。
執行畫面:編輯節點名稱時自動檢查重複。
HTREEITEM CTreeCtrlEx::FindItem(HTREEITEM hItem, CString strText)
{
if (!hItem) return NULL;
while (hItem)
{
// 1. 比對目前節點
if (GetItemText(hItem) == strText)
return hItem;
// 2. 有子節點 → 遞迴搜尋子樹
if (ItemHasChildren(hItem))
{
HTREEITEM hChild = GetChildItem(hItem);
HTREEITEM hFound = FindItem(hChild, strText);
if (hFound) return hFound;
// 子樹找不到 → 移至下一個兄弟節點 (需取得父節點再取下一個兄弟)
hItem = GetNextSiblingItem(GetParentItem(hChild));
}
else
{
// 3. 無子節點 → 直接移至下一個兄弟
hItem = GetNextSiblingItem(hItem);
if (!hItem) return NULL; // 已達樹尾
}
}
return NULL;
}
演算法說明: 深度優先搜尋 (DFS)。依序檢查「目前節點 → 子樹 → 兄弟節點」。利用 GetParentItem + GetNextSiblingItem 實現回溯至父層繼續搜尋兄弟。
| 控件 | ID | 成員變數/函式 |
|---|---|---|
| Tree Control | IDC_MYTREE | CTreeCtrl m_ctrlTree (後改為 CTreeCtrlEx) |
| Button | IDC_NEWITEM | OnNewitem() |
| 樹狀項目暫存 | - | HTREEITEM m_hInsertItem |
void CMyTreeCtrlDlg::OnNewitem()
{
HTREEITEM hSel = m_ctrlTree.GetSelectedItem();
if (!hSel) hSel = TVI_ROOT; // 無選取則掛在根下
m_hInsertItem = m_ctrlTree.InsertItem(_T("新項目"), hSel);
m_ctrlTree.SelectItem(m_hInsertItem);
m_ctrlTree.Expand(hSel, TVE_EXPAND);
// 進入編輯模式
m_ctrlTree.EditLabel(m_hInsertItem);
}
void CMyTreeCtrlDlg::OnBeginlabeleditMytree(NMHDR* pNMHDR, LRESULT* pResult)
{
TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
m_ctrlTree.GetEditControl()->LimitText(16); // 最長 16 字元
*pResult = 0;
}
void CMyTreeCtrlDlg::OnEndlabeleditMytree(NMHDR* pNMHDR, LRESULT* pResult)
{
TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
CString strName;
m_ctrlTree.GetEditControl()->GetWindowText(strName);
if (strName.IsEmpty())
{
AfxMessageBox(_T("節點名稱不可為空!"));
m_ctrlTree.EditLabel(m_hInsertItem); // 重新編輯
*pResult = 0;
return;
}
HTREEITEM hRoot = m_ctrlTree.GetRootItem();
HTREEITEM hFound = m_ctrlTree.FindItem(hRoot, strName);
if (!hFound)
{
// 未重複 → 確認對話框
CString msg; msg.Format(_T("確定新增節點「%s」嗎?"), strName);
if (MessageBox(msg, _T("提示"), MB_OKCANCEL) == IDOK)
*pResult = TRUE; // 接受新名稱
else
m_ctrlTree.DeleteItem(m_hInsertItem); // 取消 → 刪除剛建立的項目
}
else
{
AfxMessageBox(_T("該節點名稱已存在,請重新輸入!"));
m_ctrlTree.EditLabel(m_hInsertItem); // 重新編輯
*pResult = 0; // 拒絕新名稱
}
}
FindItem 為遞迴 DFS,時間複雜度 O(N),N 為節點總數,滿足一般規模樹狀結構需求。TVN_BEGINLABELEDIT 限制輸入長度;TVN_ENDLABELEDIT 執行查重與使用者確認。DeleteItem(m_hInsertItem) 清理暫存節點。測試環境:Win98 + VC6.0 通過。現代環境 (VS2019+) 同樣適用,只需注意字元集設定 (Unicode/MBCS)。