如何修改MFC CTreeCtrl中指定项的Bounding rectangle以实现地图图例功能?
嘿,我太懂你这种从Stingray转MFC的困扰了!之前用Stingray树控件改个项的 bounding rectangle简直手到擒来,换成MFC的CTreeCtrl却找不到直接的SetItemRect方法,确实头疼。不过别担心,咱们可以用MFC的**自定义绘制(Owner-Draw)**来搞定这个需求,这也是扩展原生控件功能的标准操作,刚好能满足你做地图图例的要求。
下面是具体的实现步骤,都是我实际项目里用过的靠谱方案:
第一步:给CTreeCtrl开启自绘模式
首先得把你的CTreeCtrl设置成自绘风格,推荐用TVS_OWNERDRAWVARIABLE(支持每个项用不同高度),比固定高度的TVS_OWNERDRAWFIXED更适合地图图例的场景。
你可以在对话框资源编辑器里给树控件勾选「Owner Draw Variable」,或者在代码创建控件的时候加上这个风格:
// 示例:动态创建树控件时添加自绘风格 m_myTree.Create(WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_OWNERDRAWVARIABLE, CRect(10,10,300,400), this, IDC_MY_TREE);
第二步:派生自己的CTreeCtrl子类
原生CTreeCtrl的自绘逻辑不够灵活,咱们得派生子类来处理自绘相关的消息。
1. 处理WM_MEASUREITEM消息:定义项的尺寸
这个消息会让你告诉系统每个树项的高度(间接控制宽度),刚好可以在这里给指定的图例项设置自定义宽高:
void CMyTreeCtrl::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if (lpMeasureItemStruct->CtlType != ODT_TREEVIEW) { CTreeCtrl::OnMeasureItem(nIDCtl, lpMeasureItemStruct); return; } HTREEITEM hItem = (HTREEITEM)lpMeasureItemStruct->itemID; if (!hItem) return; // 这里根据你的业务逻辑判断是不是需要改尺寸的图例项 CString itemText; GetItemText(hItem, itemText); if (itemText == _T("河流图例") || itemText == _T("山地图例")) // 替换成你的判断规则 { // 给图例项设置自定义高度,比如40像素(默认一般是16-20) lpMeasureItemStruct->itemHeight = 40; // 宽度由树控件整体宽度决定,但绘制时可以控制内容的显示范围 } else { // 普通项用默认高度 lpMeasureItemStruct->itemHeight = GetItemHeight(hItem); } CTreeCtrl::OnMeasureItem(nIDCtl, lpMeasureItemStruct); }
2. 处理WM_DRAWITEM消息:自定义绘制项内容
这一步是核心,你可以完全控制项的绘制区域和内容——相当于间接实现了修改bounding rectangle的效果,还能同时画图例的图标和文本:
void CMyTreeCtrl::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { if (lpDrawItemStruct->CtlType != ODT_TREEVIEW) { CTreeCtrl::OnDrawItem(nIDCtl, lpDrawItemStruct); return; } HTREEITEM hItem = (HTREEITEM)lpDrawItemStruct->itemID; CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); CRect itemRect = lpDrawItemStruct->rcItem; UINT itemState = lpDrawItemStruct->itemState; // 先处理选中/非选中的背景绘制 if ((itemState & ODS_SELECTED) && (lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE))) { pDC->FillSolidRect(&itemRect, GetSysColor(COLOR_HIGHLIGHT)); } else if (!(itemState & ODS_SELECTED) && (lpDrawItemStruct->itemAction & ODA_SELECT)) { pDC->FillSolidRect(&itemRect, GetSysColor(COLOR_WINDOW)); } // 判断是不是图例项,自定义绘制内容 CString itemText; GetItemText(hItem, itemText); if (itemText == _T("河流图例")) { // 调整图标绘制区域(相当于修改了项的有效bounding范围) CRect iconRect(itemRect.left + 8, itemRect.top + 5, itemRect.left + 38, itemRect.bottom - 5); // 绘制河流图例图标(这里假设你有一个ID为IDB_RIVER的位图) CBitmap bmpRiver; bmpRiver.LoadBitmap(IDB_RIVER); CDC memDC; memDC.CreateCompatibleDC(pDC); CBitmap* pOldBmp = memDC.SelectObject(&bmpRiver); pDC->BitBlt(iconRect.left, iconRect.top, iconRect.Width(), iconRect.Height(), &memDC, 0, 0, SRCCOPY); memDC.SelectObject(pOldBmp); // 绘制文本,调整文本区域 CRect textRect(iconRect.right + 10, itemRect.top, itemRect.right, itemRect.bottom); pDC->SetTextColor((itemState & ODS_SELECTED) ? GetSysColor(COLOR_HIGHLIGHTTEXT) : GetSysColor(COLOR_WINDOWTEXT)); pDC->DrawText(itemText, textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); } else if (itemText == _T("山地图例")) { // 同理绘制山地图例的图标和文本 CRect iconRect(itemRect.left + 8, itemRect.top + 5, itemRect.left + 38, itemRect.bottom - 5); CBitmap bmpMountain; bmpMountain.LoadBitmap(IDB_MOUNTAIN); CDC memDC; memDC.CreateCompatibleDC(pDC); CBitmap* pOldBmp = memDC.SelectObject(&bmpMountain); pDC->BitBlt(iconRect.left, iconRect.top, iconRect.Width(), iconRect.Height(), &memDC, 0, 0, SRCCOPY); memDC.SelectObject(pOldBmp); CRect textRect(iconRect.right + 10, itemRect.top, itemRect.right, itemRect.bottom); pDC->SetTextColor((itemState & ODS_SELECTED) ? GetSysColor(COLOR_HIGHLIGHTTEXT) : GetSysColor(COLOR_WINDOWTEXT)); pDC->DrawText(itemText, textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); } else { // 普通项用原生绘制逻辑 CTreeCtrl::OnDrawItem(nIDCtl, lpDrawItemStruct); } }
3. 别忘了添加消息映射
在子类的头文件声明消息处理函数后,要在cpp文件里添加消息映射,让系统把消息发给咱们的处理函数:
BEGIN_MESSAGE_MAP(CMyTreeCtrl, CTreeCtrl) ON_WM_MEASUREITEM() ON_WM_DRAWITEM() END_MESSAGE_MAP()
额外小提示
如果你只需要统一修改所有项的高度,也可以用TVM_SETITEMHEIGHT消息,但这个是全局的,没法针对单个项,所以还是自绘的方式更适合地图图例这种需要不同项不同尺寸的场景。
用这个方案,你不仅能自定义每个图例项的尺寸,还能完全控制图标和文本的绘制,完美替代Stingray的功能!
备注:内容来源于stack exchange,提问作者Graphman

