65.9K
CodeProject 正在变化。 阅读更多。
Home

自定义绘制的垂直树控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (36投票s)

2005年10月14日

3分钟阅读

viewsIcon

153144

downloadIcon

3318

一个 CTreeCtrl 派生类,既是普通的 CTreeCtrl,也是完全自定义绘制的垂直树控件

引言

在学习电子工程和计算机科学期间,我参加了一个编译器研讨会,我们需要编写自己的编程语言。为了查看和分析给定程序的语法树,我当时编写了一个自定义绘制的树组件。最初的组件是用 Java 编写的,我认为将其作为 CTreeCtrl 派生类可能很有用。与 CodeProject 上的一些其他自定义绘制的树控件相比,这个控件没有自己的数据结构来表示树。这意味着当您想从 CTreeCtrl 切换时,您不必编写不同的代码来插入树项。因为这个控件继承自 CTreeCtrl,所以很容易激活以传统方式绘制树的股票功能。

用于显示垂直树的算法

垂直树的绘制算法基于两个规则。第一个规则确定叶元素之间始终具有相同的空间。另一个规则指出父节点始终必须绘制在其子元素的中间。如果您以预先排序的序列递归地将这两个规则应用于树中的每个节点,那么您将得到此控件的功能。

当必须绘制树时,将为根元素调用操作 DrawItem(CDC *pDC, HTREEITEM item, int x, int level)(参考上面的图示/节点 8)。对于要绘制的第一个项目,起始 x 位置(参见 DrawItem 的参数 3)和级别为零。如上所述,该算法以预先排序的方式递归地遍历所有项目。因此,如果当前项目具有子元素,则当它为每个子节点调用 DrawItem 时,它们也会被展开。但是,如果当前节点是叶节点,或者其子节点已折叠,则 DrawItem 仅绘制当前项目。框中的数字显示了项目的绘制顺序。如您所见,首先调用 DrawItem 的根节点最后绘制。由于我们的第二个规则,这不足为奇。

绘制树节点后,DrawItem 返回项目最右侧边框的 x 位置加上 m_ItemSpaceX(参考我们的第一个规则)。对于叶项,最右边的边框是它们自己的右边框,而父元素的最右边的边框是最后一个子元素的右边框。

通常,我不喜欢在文章中展示太多的源代码,但是为了分析算法,这对我们可能很有用。

int CVerticalTree::DrawItem(CDC *pDC, HTREEITEM item, int x, int level)
{
    CString name = GetItemText(item);
    CSize text_size = pDC->GetTextExtent(name);

    int state = GetItemState(item, TVIF_STATE);
    bool selected = (state & TVIS_SELECTED) != 0;

    if (ItemHasChildren(item))
    {
        int left = x;
        int right = 0;
        int childcount = 0;

        if (state & TVIS_EXPANDED)
            for (HTREEITEM childitem = GetChildItem(item); 
                childitem != NULL; 
                    childitem = GetNextItem(childitem, TVGN_NEXT))
            {
                right = DrawItem(pDC, childitem, x, level + 1);
                x = right;
                childcount++;
            }

        right = right - m_ItemSpaceX;
        int width =  right - left;
        x = left + width / 2 - text_size.cx / 2;

        if (x < left + m_ItemSpaceX)
           x = left;
        

        int y = level*m_ItemSpaceY;
        DrawItemText(pDC, item, name, 
                    x, level*m_ItemSpaceY, 
                    text_size.cx, text_size.cy, 
                    m_ItemBkColor, m_ItemTextColor);

        //Draw lines...
        if (m_bHasLines && (state & TVIS_EXPANDED))
        {
            int xstart = x + text_size.cx / 2;
            int ystart = y + text_size.cy + BUTTON_SIZE;
            CGdiObject *oldPen = pDC->SelectObject(&m_TreeLinePen);
            for (HTREEITEM childitem = GetChildItem(item); 
                    childitem != NULL; 
                        childitem = GetNextItem(childitem, TVGN_NEXT))
            {
                ItemViewport *current = GetViewport(childitem);
                pDC->MoveTo(xstart+m_OffsetX,ystart+m_OffsetY);
                pDC->LineTo(current->x +m_OffsetX + current->cx / 2 , 
                                             current->y + m_OffsetY);
            }
            pDC->SelectObject(oldPen);
        }

        if (m_bHasButtons)
        {
            pDC->Draw3dRect(x+m_OffsetX+text_size.cx / 2 - BUTTON_SIZE / 2, 
                        y+m_OffsetY + text_size.cy, BUTTON_SIZE, 
                        BUTTON_SIZE, RGB(200,200,200), RGB(100,100,100));
            pDC->MoveTo(x+m_OffsetX+text_size.cx / 2 - BUTTON_SIZE / 2 + 2, 
                        y+m_OffsetY + text_size.cy + BUTTON_SIZE / 2);
            pDC->LineTo(x+m_OffsetX+text_size.cx / 2 + BUTTON_SIZE / 2 - 1, 
                        y+m_OffsetY + text_size.cy + BUTTON_SIZE / 2);

            if ((state & TVIS_EXPANDED) == 0)
            {
                pDC->MoveTo(x+m_OffsetX+text_size.cx / 2, 
                        y+m_OffsetY + text_size.cy + 2);
                pDC->LineTo(x+m_OffsetX+text_size.cx / 2, 
                        y+m_OffsetY + text_size.cy + BUTTON_SIZE - 2);
            }
        }
        if (right > x + text_size.cx)
           return right + m_ItemSpaceX;
        else
           return x + text_size.cx + m_ItemSpaceX;
    }
    else
    {
        DrawItemText(pDC, item, name, x, level*m_ItemSpaceY, 
                    text_size.cx, text_size.cy, 
                    m_LeafBkColor, m_LeafTextColor);
        return x + text_size.cx + m_ItemSpaceX;
    }
}

缺陷和不足

最初的 Java 控件在绘图例程中进行了一些优化,我无法在此 MFC 版本中进行调整。如果您查看源代码,您会发现即使项目不可见,对 DrawItem 的递归调用也不会停止。因此,在某些情况下,重绘控件可能非常昂贵。令人惊讶的是,此控件以大约 10000 个元素的速度运行得非常快。因此,如果您不打算处理大量的元素,那么就用它吧。但是,如果您想在应用程序中使用它,则应牢记这一点。

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.