如何用MFC CTreeCtrl替代Stingray控件实现多尺寸图标树?
用MFC CTreeCtrl实现多尺寸图标树控件
完全可以通过自定义CTreeCtrl子类,结合自定义绘制和动态调整树项高度来替代Stingray的多尺寸图标树控件效果,具体实现代码如下:
1. 自定义CTreeCtrl子类定义
class CMultiSizeIconTreeCtrl : public CTreeCtrl { DECLARE_DYNAMIC(CMultiSizeIconTreeCtrl) public: CMultiSizeIconTreeCtrl(); virtual ~CMultiSizeIconTreeCtrl(); protected: // 存储每个树项的图标句柄和对应尺寸 CMap<HTREEITEM, HTREEITEM, HICON, HICON> m_mapItemIcons; CMap<HTREEITEM, HTREEITEM, CSize, CSize> m_mapItemIconSizes; // 消息处理函数 afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult); afx_msg LRESULT OnSetItemHeight(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() public: // 为指定树项设置自定义图标及尺寸 void SetItemCustomIcon(HTREEITEM hItem, HICON hIcon, const CSize& iconSize); };
2. 子类实现代码
IMPLEMENT_DYNAMIC(CMultiSizeIconTreeCtrl, CTreeCtrl) CMultiSizeIconTreeCtrl::CMultiSizeIconTreeCtrl() { } CMultiSizeIconTreeCtrl::~CMultiSizeIconTreeCtrl() { // 释放所有图标资源 POSITION pos = m_mapItemIcons.GetStartPosition(); HTREEITEM hItem; HICON hIcon; while (pos) { m_mapItemIcons.GetNextAssoc(pos, hItem, hIcon); if (hIcon) DestroyIcon(hIcon); } } BEGIN_MESSAGE_MAP(CMultiSizeIconTreeCtrl, CTreeCtrl) ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CMultiSizeIconTreeCtrl::OnCustomDraw) ON_MESSAGE(TVM_SETITEMHEIGHT, &CMultiSizeIconTreeCtrl::OnSetItemHeight) END_MESSAGE_MAP() void CMultiSizeIconTreeCtrl::SetItemCustomIcon(HTREEITEM hItem, HICON hIcon, const CSize& iconSize) { if (!hItem || !hIcon) return; m_mapItemIcons[hItem] = hIcon; m_mapItemIconSizes[hItem] = iconSize; RedrawItems(hItem, hItem); // 刷新该项 } LRESULT CMultiSizeIconTreeCtrl::OnSetItemHeight(WPARAM wParam, LPARAM lParam) { // 拦截默认统一设置项高度的消息,改为动态控制每个项高度 return 0; } void CMultiSizeIconTreeCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) { NMTVCUSTOMDRAW* pTVCD = reinterpret_cast<NMTVCUSTOMDRAW*>(pNMHDR); *pResult = CDRF_DODEFAULT; switch (pTVCD->nmcd.dwDrawStage) { case CDDS_PREPAINT: *pResult = CDRF_NOTIFYITEMDRAW; break; case CDDS_ITEMPREPAINT: { HTREEITEM hItem = pTVCD->nmcd.dwItemSpec; CSize iconSize; if (m_mapItemIconSizes.Lookup(hItem, iconSize)) { // 调整当前项高度为图标高度加边距 pTVCD->nmcd.rcItem.bottom = pTVCD->nmcd.rcItem.top + iconSize.cy + 4; } *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_SKIPDEFAULT; break; } case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { HTREEITEM hItem = pTVCD->nmcd.dwItemSpec; HICON hIcon; CSize iconSize; if (m_mapItemIcons.Lookup(hItem, hIcon) && m_mapItemIconSizes.Lookup(hItem, iconSize)) { // 计算图标居中绘制位置 int x = pTVCD->nmcd.rcItem.left + (pTVCD->nmcd.rcItem.right - pTVCD->nmcd.rcItem.left - iconSize.cx) / 2; int y = pTVCD->nmcd.rcItem.top + (pTVCD->nmcd.rcItem.bottom - pTVCD->nmcd.rcItem.top - iconSize.cy) / 2; // 绘制图标 DrawIcon(pTVCD->nmcd.hdc, x, y, hIcon); // 绘制树项文本(可根据需求调整位置) CString strText; GetItemText(hItem, strText); RECT textRect = pTVCD->nmcd.rcItem; textRect.left += iconSize.cx + 8; // 图标与文本间距8px DrawText(pTVCD->nmcd.hdc, strText, strText.GetLength(), &textRect, DT_VCENTER | DT_SINGLELINE); } *pResult = CDRF_SKIPDEFAULT; break; } } }
3. 实际使用示例
在对话框或窗口中集成该控件:
// 对话框类中声明成员变量 CMultiSizeIconTreeCtrl m_multiSizeTree; // 在OnInitDialog中初始化 BOOL CYourDialog::OnInitDialog() { CDialogEx::OnInitDialog(); // 方式1:动态创建控件 m_multiSizeTree.Create(WS_VISIBLE | WS_CHILD | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS, CRect(20, 20, 350, 450), this, IDC_MULTI_SIZE_TREE); // 方式2:绑定资源编辑器中的树控件(需在类向导中关联自定义子类) // 添加带不同尺寸图标的树项 HTREEITEM hBigItem = m_multiSizeTree.InsertItem(_T("大图标节点")); HICON hBigIcon = AfxGetApp()->LoadIcon(IDI_ICON_32); // 32x32尺寸图标 m_multiSizeTree.SetItemCustomIcon(hBigItem, hBigIcon, CSize(32, 32)); HTREEITEM hSmallItem = m_multiSizeTree.InsertItem(_T("小图标节点")); HICON hSmallIcon = AfxGetApp()->LoadIcon(IDI_ICON_16); // 16x16尺寸图标 m_multiSizeTree.SetItemCustomIcon(hSmallItem, hSmallIcon, CSize(16, 16)); return TRUE; }
关键实现说明
- 通过
NM_CUSTOMDRAW消息接管树控件的绘制逻辑,跳过默认绘制,自主控制图标和文本的渲染 - 用
CMap存储每个树项的图标和尺寸信息,实现不同节点的差异化显示 - 动态调整每个树项的绘制区域高度,确保大图标能完整展示
- 在析构函数中释放图标资源,避免内存泄漏
内容的提问来源于stack exchange,提问作者Graphman
相关产品推荐
相关产品推荐

