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



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM