MFC CTreeCtrl派生类CMytreeCtrl展开折叠图标及节点图标切换异常求助
我来帮你排查下这个问题哈,你遇到的子节点展开图标不切换、自带展开折叠箭头失效的问题,主要是几个容易踩的小坑导致的,咱们一步步解决:
1. 图像列表的设置与图标添加有误
你当前只给TVSIL_NORMAL设置了图像列表,但CTreeCtrl的展开/折叠箭头图标是关联到TVSIL_STATE图像列表的,只设置普通列表会导致默认箭头无法显示。另外,你只加载了一张位图并添加了1个图标(索引仅为0),但代码里直接用了索引1和2,这肯定会找不到对应的图标。
修正图像列表的创建与添加
首先确保你的IDB_BITMAP5是横向排列的16x16图标集合(至少包含3个图标,对应索引0、1、2),然后按下面的代码调整:
// 先清理旧的图像列表(避免重复创建) if (m_imageList.GetSafeHandle()) m_imageList.DeleteImageList(); // 创建带透明通道支持的图像列表,ILC_MASK是处理透明色的关键 m_imageList.Create(16, 16, ILC_COLOR32 | ILC_MASK, 3, 0); m_bitmap.LoadBitmap(IDB_BITMAP5); // 从位图中提取所有图标(自动按16x16的尺寸拆分) m_imageList.Add(&m_bitmap, RGB(255, 0, 255)); // 同时设置普通图标列表和状态图标列表(状态列表负责显示展开折叠箭头) SetImageList(&m_imageList, TVSIL_NORMAL); SetImageList(&m_imageList, TVSIL_STATE);
2. 误用了SetItemImage的参数
这里是最容易搞错的点:MFC的CTreeCtrl::SetItemImage函数,第二个参数是未展开/未选中时的图标索引,第三个参数是选中时的图标索引,根本不是展开时的图标!展开状态的图标需要通过TVITEM结构的iExpandedImage成员来设置。
正确设置节点的展开/折叠图标
不管是创建新节点还是修改已有节点,都要用TVITEM来配置展开图标:
// 方式1:创建子节点时直接设置 TVINSERTSTRUCT tvInsert; tvInsert.hParent = hTreeItemRoot; tvInsert.hInsertAfter = TVI_LAST; tvInsert.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_EXPANDEDIMAGE | TVIF_EXPANDEDSELECTEDIMAGE; tvInsert.item.pszText = _T("目标子节点"); tvInsert.item.iImage = 1; // 未展开、未选中的图标索引 tvInsert.item.iSelectedImage = 1; // 未展开、选中时的图标索引 tvInsert.item.iExpandedImage = 2; // 展开、未选中时的图标索引 tvInsert.item.iExpandedSelectedImage = 2; // 展开、选中时的图标索引 HTREEITEM hTreeItem = InsertItem(&tvInsert); // 方式2:修改已存在的节点 TVITEM tvItem; tvItem.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_EXPANDEDIMAGE | TVIF_EXPANDEDSELECTEDIMAGE; tvItem.hItem = hTreeItem; tvItem.iImage = 1; tvItem.iSelectedImage = 1; tvItem.iExpandedImage = 2; tvItem.iExpandedSelectedImage = 2; SetItem(&tvItem);
3. 未处理展开/折叠的通知消息(可选但推荐)
如果需要在节点展开/折叠时动态切换图标(比如根据节点类型不同显示不同图标),可以处理TVN_ITEMEXPANDED通知消息:
第一步:添加消息映射
在你的CMytreeCtrl类头文件中声明消息处理函数:
afx_msg void OnItemExpanded(NMHDR* pNMHDR, LRESULT* pResult);
在类的cpp文件中添加消息映射:
BEGIN_MESSAGE_MAP(CMytreeCtrl, CTreeCtrl) ON_NOTIFY_REFLECT(TVN_ITEMEXPANDED, &CMytreeCtrl::OnItemExpanded) END_MESSAGE_MAP()
第二步:实现消息处理函数
void CMytreeCtrl::OnItemExpanded(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; HTREEITEM hItem = pNMTreeView->itemNew.hItem; // 判断是否是你需要设置特殊图标的子节点(比如通过GetItemData区分) if (GetItemData(hItem) == YOUR_NODE_ID) { if (pNMTreeView->action == TVE_EXPAND) { // 展开时设置图标索引2 TVITEM tvItem; tvItem.mask = TVIF_EXPANDEDIMAGE | TVIF_EXPANDEDSELECTEDIMAGE; tvItem.hItem = hItem; tvItem.iExpandedImage = 2; tvItem.iExpandedSelectedImage = 2; SetItem(&tvItem); } else if (pNMTreeView->action == TVE_COLLAPSE) { // 折叠时恢复图标索引1 TVITEM tvItem; tvItem.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE; tvItem.hItem = hItem; tvItem.iImage = 1; tvItem.iSelectedImage = 1; SetItem(&tvItem); } } *pResult = 0; }
4. 检查树控件的样式设置
默认展开折叠箭头不显示,大概率是没开启对应的控件样式。你可以在资源编辑器中选中树控件,在“样式”里勾选有按钮、有连线、根节点有连线;或者用代码设置:
ModifyStyle(0, TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT);
按照上面的步骤调整后,应该就能实现你想要的“子节点展开显示图标2,折叠显示图标1”的效果,同时默认的展开折叠箭头也能正常工作啦。
内容的提问来源于stack exchange,提问作者Vicky

