Merlyn

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

绘制二叉树

在学习二叉树的时候,二叉树里的数据不容易直观地体现出来,因为它的结构比较特殊,不能很好发挥 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()
	{
		PostMessage(WM_QUIT);
		::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 (y && (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);
		}

                DestroyWindow();
		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;
};


posted on 2019-07-04 19:02  Merlyn  阅读(1713)  评论(0编辑  收藏  举报