在 VS2019中用 C++ 中绘制二叉树

绘制二叉树

在学习二叉树的时候,二叉树里的数据不容易直观地体现出来,因为它的结构比较特殊,不能很好发挥 print 大法

我写了一个比较简单实用的 Windows 窗口来绘制二叉树,用 GDI 函数绘制的,如果觉得太粗糙可以很容易修改成 GDI+ 

image_

使用也比较简单, 只需要写一个回调函数用来填充 NodeInfo 结构体就好了

struct NodeInfo
{
    void* left;
    void* right;
    COLORREF ColorBorder;
    COLORREF ColorFill;
    COLORREF ColorLabel;
    TCHAR  Label[256];
};

比如有这样一个二叉根节点, 只需要填充好 NodeInfo 的左右节点和显示的文本就好了,还可以根据节点内容设置字体颜色、边框颜色和填充颜色
在绘制的时候会遍历整个二叉树,会为每个节点调用一次回调函数
回调函数的第一个参数是 DisplayBinaryTree 方法设置的回调上下文参数, 第二个参数是当前二叉树节点指针,第三个参数是当前二叉树节点指针在整个树的层级, 第四个参数是输出参数 NodeInfo 指针,回调函数返回前必须要设置好 left ,right 和 Label 字段。如果使用了哨兵,如果当前节点的子节点是哨兵,必须把对应的 left 或者right 设置为空指针, 

struct node
{
    node* left;
    node* right;

    int key;
};

void NodeInfoCallback(void* context, void* node_, int hierarchy, NodeInfo* nodeInfo)
{
    auto x = (node*)(node_);              

    nodeInfo->left = x->left;           // 设置左节点
    nodeInfo->right = x->right;         // 设置右节点

    _stprintf_s(nodeInfo->Label, L"%d", x->key);    // 需要显示的文本

    //nodeInfo->ColorFill = RGB(255, 0, 0);           // 填充色
    //nodeInfo->ColorLabel = RGB(0, 0, 0);          // 文本颜色
    //nodeInfo->ColorBorder = RGB(0,128 ,128);        // 边框颜色
}

写好回调函数后, 可以实例化一个 TreeWindow 对象调用 DisplayBinaryTree 方法,传入根节点指针,回调和一个可选的回调上下文参数

    TreeWindow wnd;
    wnd.DisplayBinaryTree(root, NodeInfoCallback, nullptr);

TreeWindow 窗口类代码 


#include <atlbase.h>
#include <atlwin.h>


struct NodeInfo
{
    void* left;
    void* right;
    COLORREF ColorBorder;
    COLORREF ColorFill;
    COLORREF ColorLabel;
    TCHAR  Label[256];
};

class TreeWindow : public CWindowImpl<TreeWindow, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW> >
{
public:
    TreeWindow()
    {
        _thread.Attach(AtlCreateThread<TreeWindow>(&TreeWindow::_threadCallback, this));
        
        while (!(const_cast<volatile HWND&>(m_hWnd)))
        {
            Sleep(100);
        }

    }

    ~TreeWindow()
    {
        SendMessage(WM_CLOSE);
        ::WaitForSingleObject(_thread, INFINITE);
        _thread.Close();
    }

    BEGIN_MSG_MAP(TreeWindow)
        MESSAGE_HANDLER(WM_PAINT, OnPaint)
    END_MSG_MAP()

    void DisplayBinaryTree(void* tree, void (*nodeInfoCallback)(void*, void*, int, NodeInfo*), void* context)
    {
        ATLASSERT(tree);
        ATLASSERT(nodeInfoCallback);

        if (tree && nodeInfoCallback)
        {
            _tree = tree;
            _nodeInfoCallback = nodeInfoCallback;
            _context = context;

            Refresh();
        }
        else
        {
            _tree = nullptr;
            _nodeInfoCallback = nullptr;
        }
    }

    void Refresh()
    {
        Invalidate();
        UpdateWindow();
    }

protected:
    LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        PAINTSTRUCT sp;
        auto dc = BeginPaint(&sp);

        SetTextAlign(dc, TA_CENTER | TA_BOTTOM);
        SelectObject(dc, GetStockObject(DEFAULT_GUI_FONT));

        RECT rect;
        GetClientRect(&rect);

        auto width = rect.right - rect.left;
        auto height = rect.bottom - rect.top;

        auto halfWidth = width / 2;
        auto halfHeight = height / 2;

        POINT nodePos = { halfWidth, _rowSpacing };
        POINT nextNodePos = nodePos;


        if (_tree && _nodeInfoCallback)
        {
            
            void* nodeStack[256] = { _tree };       //节点栈, 保存当前访问节点在二叉树中的路径
            POINT nodePosStack[256] = { nodePos };  //节点栈中每个节点的位置, 
            int n = 1;   //

            void* y = nullptr;   // 临时变量, 保存最后访问的节点,用来维护层级
            int h = 1;    // 当前节点在二叉根中的层级
            
            while (n > 0)
            {
                void* x = nodeStack[n - 1];    // 取出当前节点
                nodePos = nodePosStack[n - 1]; // 取出当前位置

                NodeInfo nodeInfo = {};
                nodeInfo.ColorFill = 0xffffff;
                _nodeInfoCallback(_context, x, h, &nodeInfo);

                if (nodeInfo.left || nodeInfo.right)
                {
                    if (nodeInfo.left == y || nodeInfo.right == y)
                    {
                        n--;       // 退栈
                        h--;
                    }
                    else
                    {
                        if (nodeInfo.right)
                        {
                            nextNodePos.y = nodePos.y + _rowSpacing;
                            nextNodePos.x = nodePos.x + (halfWidth - 50) / ( 1 << h);

                            MoveToEx(dc, nodePos.x, nodePos.y, nullptr);
                            LineTo(dc, nextNodePos.x, nextNodePos.y);

                            nodeStack[n] = nodeInfo.right;
                            nextNodePos.y += 10;
                            
                            nodePosStack[n] = nextNodePos;
                            n++;
                        }


                        if (nodeInfo.left)
                        {
                            nextNodePos.y = nodePos.y + _rowSpacing;
                            nextNodePos.x = nodePos.x - (halfWidth - 50) / (1 << h);

                            MoveToEx(dc, nodePos.x, nodePos.y, nullptr);
                            LineTo(dc, nextNodePos.x, nextNodePos.y);


                            nodeStack[n] = nodeInfo.left;
                            nextNodePos.y += 10;
                            nodePosStack[n] = nextNodePos;
                            n++;
                        }

                        DrawNode(dc, &nodePos, &nodeInfo);

                        h++;
                    }

                }
                else
                {
                    DrawNode(dc, &nodePos, &nodeInfo);
                    n--;    
                }

                y = x;
            }
        }

        EndPaint(&sp);
        return 0;
    }

    virtual DWORD _ThreadCallback()
    {
        MSG msg;

        Create(nullptr);
        UpdateWindow();
        ShowWindow(SW_SHOW);

        while (GetMessage(&msg, 0, 0, 0))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }

        return 0;
    }

    virtual void DrawNode(HDC dc, LPPOINT pos, NodeInfo* nodeInfo)
    {
        COLORREF textColor = SetTextColor(dc, nodeInfo->ColorLabel);
        HBRUSH brush = CreateSolidBrush(nodeInfo->ColorFill);
        HPEN borderPen = CreatePen(PS_SOLID, 1, nodeInfo->ColorBorder);
        ATLASSERT(brush);
        HGDIOBJ oldBrush = SelectObject(dc, brush);
        HGDIOBJ oldPen = SelectObject(dc, borderPen);
        int oldBkMode = SetBkMode(dc, TRANSPARENT);

        SIZE size;
        int labelLen = _tcslen(nodeInfo->Label);
        ::GetTextExtentPoint32(dc, nodeInfo->Label, labelLen, &size);

        size.cx += 20;
        size.cy += 20;
        RECT rect = { 0,0, size.cx , size.cy };

        ::OffsetRect(&rect, pos->x - size.cx / 2, pos->y - size.cy  + 10 );

        ::Ellipse(dc, rect.left, rect.top, rect.right, rect.bottom);

        if (nodeInfo->Label[0])
            TextOut(dc, pos->x, pos->y, nodeInfo->Label, labelLen);

        SetBkMode(dc, oldBkMode);
        SelectObject(dc, oldPen);
        SelectObject(dc, oldBrush);
        SetTextColor(dc, textColor);

        DeleteObject(brush);
        DeleteObject(borderPen);
        
    }

private:
    static DWORD CALLBACK _threadCallback(TreeWindow* pthis)
    {
        return pthis->_ThreadCallback();
    }

private:
    CHandle _thread;
    void* _tree = nullptr;
    void* _context = nullptr;
    void (*_nodeInfoCallback)(void* , void*, int, NodeInfo*) = nullptr;
    int _rowSpacing = 48;
};

猜你喜欢

转载自www.cnblogs.com/Merlyn7/p/11134183.html