一、树控件概述
1.1 什么是树控件
树控件(Tree Control)是Windows通用控件之一,用于显示分层数据,以树形结构展示信息。在MFC中,树控件由CTreeCtrl类封装。
1.2 主要特点
-
层次化显示数据
-
支持展开/折叠节点
-
支持图标和文本
-
支持拖放操作
-
支持多选(需要特定风格)
1.3 常见应用场景
-
文件资源管理器目录结构
-
组织结构图
-
配置选项分类
-
数据库导航
二、创建树控件
2.1 通过资源编辑器创建
在对话框资源中添加Tree Control控件
设置控件属性:
-
ID:如IDC_TREE1
-
Has buttons:显示展开/折叠按钮
-
Has lines:显示连接线
-
Lines at root:根节点连接线
-
Border:边框
-
Check boxes:复选框(可选)
2.2 动态创建
// 在对话框类中声明
CTreeCtrl m_treeCtrl;
// 在OnInitDialog()中创建
BOOL CMyDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
CRect rect(10, 10, 300, 400);
m_treeCtrl.Create(WS_VISIBLE | WS_CHILD | WS_BORDER |
TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS,
rect, this, IDC_TREE1);
return TRUE;
}
2.3 常用样式说明
| TVS_HASLINES | 显示父子节点间的连线 |
| TVS_LINESATROOT | 根节点显示连线 |
| TVS_HASBUTTONS | 显示展开/折叠按钮 |
| TVS_EDITLABELS | 允许编辑节点文本 |
| TVS_SHOWSELALWAYS | 失去焦点时仍显示选择 |
| TVS_CHECKBOXES | 显示复选框 |
| TVS_FULLROWSELECT | 整行选择(XP风格) |
| TVS_TRACKSELECT | 跟踪选择 |
| TVS_DISABLEDRAGDROP | 禁用拖放 |
三、树控件的基本操作
3.1 插入节点
// 插入根节点
HTREEITEM hRoot = m_treeCtrl.InsertItem(_T("根节点"), TVI_ROOT);
// 插入子节点
HTREEITEM hChild1 = m_treeCtrl.InsertItem(_T("子节点1"), hRoot);
HTREEITEM hChild2 = m_treeCtrl.InsertItem(_T("子节点2"), hRoot);
// 在指定位置插入
HTREEITEM hChild3 = m_treeCtrl.InsertItem(_T("子节点3"), hRoot, TVI_LAST);
HTREEITEM hChild4 = m_treeCtrl.InsertItem(_T("子节点4"), hRoot, TVI_FIRST);
HTREEITEM hChild5 = m_treeCtrl.InsertItem(_T("子节点5"), hRoot, hChild2); // 插入到hChild2之前
// 带图标的节点插入
HTREEITEM hItemWithIcon = m_treeCtrl.InsertItem(_T("带图标的节点"),
0, // 图标索引
1, // 选中时的图标索引
hRoot);
3.2 设置节点数据
每个树节点可以关联一个32位的自定义数据(DWORD_PTR):
// 定义数据结构
struct CMyItemData {
int nID;
CString strType;
void* pData;
};
// 设置节点数据
CMyItemData* pData = new CMyItemData;
pData->nID = 1001;
pData->strType = _T("Folder");
pData->pData = NULL;
m_treeCtrl.SetItemData(hItem, (DWORD_PTR)pData);
// 获取节点数据
CMyItemData* pRetrievedData = (CMyItemData*)m_treeCtrl.GetItemData(hItem);
// 重要:在节点删除时需要清理内存
3.3 节点操作
// 获取选中节点
HTREEITEM hSelected = m_treeCtrl.GetSelectedItem();
// 获取父节点
HTREEITEM hParent = m_treeCtrl.GetParentItem(hSelected);
// 获取第一个子节点
HTREEITEM hChild = m_treeCtrl.GetChildItem(hSelected);
// 获取下一个兄弟节点
HTREEITEM hNextSibling = m_treeCtrl.GetNextSiblingItem(hSelected);
// 获取上一个兄弟节点
HTREEITEM hPrevSibling = m_treeCtrl.GetPrevSiblingItem(hSelected);
// 获取根节点
HTREEITEM hRoot = m_treeCtrl.GetRootItem();
// 展开/折叠节点
m_treeCtrl.Expand(hItem, TVE_EXPAND); // 展开
m_treeCtrl.Expand(hItem, TVE_COLLAPSE); // 折叠
m_treeCtrl.Expand(hItem, TVE_TOGGLE); // 切换状态
// 选择节点
m_treeCtrl.SelectItem(hItem);
m_treeCtrl.Select(hItem, TVGN_CARET);
// 确保节点可见
m_treeCtrl.EnsureVisible(hItem);
// 删除节点
m_treeCtrl.DeleteItem(hItem); // 删除单个节点
m_treeCtrl.DeleteAllItems(); // 删除所有节点
3.4 遍历树节点
// 递归遍历所有节点
void CMyDialog::TraverseTree(HTREEITEM hItem)
{
if (hItem == NULL) return;
// 处理当前节点
CString strText = m_treeCtrl.GetItemText(hItem);
TRACE(_T("节点: %s\\n"), strText);
// 递归遍历子节点
HTREEITEM hChild = m_treeCtrl.GetChildItem(hItem);
while (hChild != NULL)
{
TraverseTree(hChild);
hChild = m_treeCtrl.GetNextSiblingItem(hChild);
}
}
// 调用方法
HTREEITEM hRoot = m_treeCtrl.GetRootItem();
TraverseTree(hRoot);
四、图标管理
4.1 图像列表(CImageList)
// 在对话框类中添加成员变量
CImageList m_imageList;
// 初始化图像列表
BOOL CMyDialog::OnInitDialog()
{
// … 其他初始化
// 创建图像列表
m_imageList.Create(16, 16, ILC_COLOR32 | ILC_MASK, 4, 1);
// 添加图标(从资源)
HICON hIcon = AfxGetApp()->LoadIcon(IDI_ICON1);
m_imageList.Add(hIcon);
DestroyIcon(hIcon);
hIcon = AfxGetApp()->LoadIcon(IDI_ICON2);
m_imageList.Add(hIcon);
DestroyIcon(hIcon);
// 关联图像列表到树控件
m_treeCtrl.SetImageList(&m_imageList, TVSIL_NORMAL);
return TRUE;
}
// 插入带图标的节点
HTREEITEM hItem = m_treeCtrl.InsertItem(_T("节点文本"),
0, // 正常图标索引
1, // 选中图标索引
TVI_ROOT);
4.2 动态设置图标
// 设置节点图标
m_treeCtrl.SetItemImage(hItem, 2, 2); // 设置正常和选中图标索引
// 获取节点图标索引
int nImage, nSelectedImage;
m_treeCtrl.GetItemImage(hItem, nImage, nSelectedImage);
五、复选框功能
5.1 启用复选框
// 创建时指定样式
m_treeCtrl.Create(WS_VISIBLE | WS_CHILD | WS_BORDER |
TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_CHECKBOXES,
rect, this, IDC_TREE1);
// 动态修改样式(不推荐,最好在创建时指定)
LONG lStyle = GetWindowLong(m_treeCtrl.m_hWnd, GWL_STYLE);
lStyle |= TVS_CHECKBOXES;
SetWindowLong(m_treeCtrl.m_hWnd, GWL_STYLE, lStyle);
5.2 复选框操作
// 获取复选框状态
UINT nState = m_treeCtrl.GetItemState(hItem, TVIS_STATEIMAGEMASK);
BOOL bChecked = ((nState >> 12) – 1) == 1; // 1-未选,2-选中
// 设置复选框状态
m_treeCtrl.SetItemState(hItem, INDEXTOSTATEIMAGEMASK(2), TVIS_STATEIMAGEMASK); // 选中
m_treeCtrl.SetItemState(hItem, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK); // 未选
六、编辑标签
6.1 启用编辑功能
// 创建时包含TVS_EDITLABELS样式
m_treeCtrl.Create(WS_VISIBLE | WS_CHILD | WS_BORDER |
TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_EDITLABELS,
rect, this, IDC_TREE1);
// 开始编辑
m_treeCtrl.EditLabel(hItem);
6.2 处理编辑事件
在对话框的消息映射中添加:
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_NOTIFY(TVN_BEGINLABELEDIT, IDC_TREE1, OnTvnBeginlabeleditTree)
ON_NOTIFY(TVN_ENDLABELEDIT, IDC_TREE1, OnTvnEndlabeleditTree)
END_MESSAGE_MAP()
事件处理函数:
void CMyDialog::OnTvnBeginlabeleditTree(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);
// 可以在此处控制哪些节点可编辑
if (/* 某些条件 */)
{
*pResult = 1; // 禁止编辑
}
else
{
*pResult = 0; // 允许编辑
}
}
void CMyDialog::OnTvnEndlabeleditTree(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);
if (pTVDispInfo->item.pszText != NULL)
{
// 更新节点文本
m_treeCtrl.SetItemText(pTVDispInfo->item.hItem, pTVDispInfo->item.pszText);
*pResult = 1; // 接受编辑
}
else
{
*pResult = 0; // 取消编辑
}
}
七、拖放操作
7.1 启用拖放
// 创建时指定TVS_DISABLEDRAGDROP禁用,或不指定以启用
// 处理拖放事件
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_NOTIFY(TVN_BEGINDRAG, IDC_TREE1, OnTvnBegindragTree)
END_MESSAGE_MAP()
7.2 实现拖放功能
void CMyDialog::OnTvnBegindragTree(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
// 设置拖放图像
m_hDragItem = pNMTreeView->itemNew.hItem;
// 创建拖放图像(使用节点文本)
CString strText = m_treeCtrl.GetItemText(m_hDragItem);
m_treeCtrl.CreateDragImage(m_hDragItem);
// 开始拖放操作
m_treeCtrl.BeginDrag(0); // 0表示使用默认光标
// 设置捕获
SetCapture();
*pResult = 0;
}
// 在WM_MOUSEMOVE和WM_LBUTTONUP中处理拖放逻辑
八、通知消息处理
8.1 常用通知消息
| TVN_SELCHANGED | 选择改变 |
| TVN_ITEMEXPANDED | 节点展开/折叠 |
| TVN_BEGINLABELEDIT | 开始标签编辑 |
| TVN_ENDLABELEDIT | 结束标签编辑 |
| TVN_BEGINDRAG | 开始拖放 |
| TVN_KEYDOWN | 按键事件 |
| NM_CLICK | 单击事件 |
| NM_DBLCLK | 双击事件 |
| NM_RCLICK | 右键单击 |
8.2 消息处理示例
// 消息映射
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_NOTIFY(TVN_SELCHANGED, IDC_TREE1, OnTvnSelchangedTree)
ON_NOTIFY(NM_CLICK, IDC_TREE1, OnNMClickTree)
ON_NOTIFY(NM_DBLCLK, IDC_TREE1, OnNMDblclkTree)
ON_NOTIFY(NM_RCLICK, IDC_TREE1, OnNMRClickTree)
END_MESSAGE_MAP()
// 选择改变事件
void CMyDialog::OnTvnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
HTREEITEM hSelected = pNMTreeView->itemNew.hItem;
if (hSelected != NULL)
{
CString strText = m_treeCtrl.GetItemText(hSelected);
// 根据选择更新其他控件
}
*pResult = 0;
}
// 右键单击事件
void CMyDialog::OnNMRClickTree(NMHDR* pNMHDR, LRESULT* pResult)
{
CPoint point;
GetCursorPos(&point);
m_treeCtrl.ScreenToClient(&point);
HTREEITEM hItem = m_treeCtrl.HitTest(point, NULL);
if (hItem != NULL)
{
m_treeCtrl.SelectItem(hItem);
// 显示上下文菜单
ShowContextMenu(hItem, point);
}
*pResult = 0;
}






