Qt實現表格樹控件-自繪樹節點虛線


原文鏈接:Qt實現表格樹控件-自繪樹節點虛線

一、開心一刻

一程序員第一次上女朋友家她媽板着臉問 :你想娶我女兒,有多少存款?

程序員低了下頭:五百!

她媽更鄙視了:才五百塊,買個廁所都不夠!

程序員忙說:不是人民幣!

她媽:就算是美元,還是不夠買廁所!

程序員:其實是比特幣!

她媽:哇,賢婿,我給你買只大龍蝦去

二、自繪樹節點?

自繪樹節點?聽起來都挺復雜的,可是為什么還要自繪樹節點呢?這充分說明產品的腦子是什么東西都能想出來的。

有一天產品說我們的軟件里缺少一個美麗的樹控件,然后就要求開發去實現這個功能。

對於有一定開發經驗的同學可能直接會去百度,或者上Qt幫助文檔上查找資料,然后發現直接設置qss就能達到我們需要的效果,於是一頓操作后,發現效果還是不錯滴。

setStyleSheet(""
	"QTreeView {outline:none;show-decoration-selected: 1;}"
	"QTreeView {outline:none;border:0px;}"
	"QTreeView::branch{		background-color: transparent;	}"
	"QTreeView::item:hover, QTreeView::branch:hover { background-color: transparent;border-color: rgb(255, 0, 0);}"
	"QTreeView::item:selected, QTreeView::branch:selected { background-color: #C5E0F7;}"
	"QTreeView::branch:open:has-children{image: url(:/branch-expand.png);}"
	"QTreeView::branch:closed:has-children{image: url(:/branch-collapse.png);}"
	"QTreeView::branch:has-siblings:!adjoins-item{border-image:url(:/branch-line.png) 0;}"
	"QTreeView::branch:has-siblings:adjoins-item{border-image:url(:/branch-more.png) 0;}"
	"QTreeView::branch:!has-children:!has-siblings:adjoins-item{border-image:url(:/branch-end.png) 0;	}"
	"QTreeView::branch:has-children:!has-siblings:closed,QTreeView::branch:closed:has-children:has-siblings{border-image:none;image: url(:/branch-collapse.png);	}"
	"QTreeView::branch:open:has-children:!has-siblings,QTreeView::branch:open:has-children:has-siblings{border-image:none;image: url(:/branch-expand.png);	}"
	);

遂找來產品驗證,當產品看到這個效果后,臉直接都綠了。

產品:我不是說要一個樹形控件嗎?行高需要能動態調整那種!

開發:。。。

開發:行高調整了,那branch上貼的圖拉伸后不是模糊了么。。。

產品:。。。

產品:我不管,這個行高可拖拽功能很重要,怎么實現我不管,但是功能必須要有。

開發:卧槽,看來只有出終極大法了,直接自繪吧

三、效果展示

如下圖所示,是一個簡單的樹branch自繪效果。

此處主要是展示一個demo效果,如果需要美化需要專業設計師出圖來做。

四、實現思路

既然要自己繪制樹形節點,那必然要去研究Qt的源碼。

1、可擴展接口

首先我們打開QTreeView類的幫助文檔,查找這個類都有哪些可供重寫的接口,然后就發現了這么幾個函數

看名字大概都知道是什么意思,不過這里還是做簡要說明

函數名 含義
drawBranches 繪制branch
drawRow 繪制行
drawTree 繪制樹
indexRowSizeHint 默認行高
rowHeight 獲取行高

前邊提到我們要自己繪制branch線條,但是其余的東西還是要走Qt默認的繪制風格,因此在重寫繪制函數時,千萬不要忘記了調用原有的繪制方法。

表格中前3個函數就是繪制樹控件的具體方法,這3個函數搭配起來完成了樹控件內容格子的繪制。下面我們來重寫這3個函數,分別完成我們的需求

2、函數重寫

a、繪制行drawRow

drawRow顧名思義就是繪制一行的意思,這里也確實如此。為什么要重寫這個函數呢?答案也很簡單。

樹控件本身是不具有垂直分割線的,既然我們要模擬表格的樣式,那么垂直分割線必然是需要的。

實現代碼可能像下面這樣,是不是很簡單。

void FrozenTreeView::drawRow(QPainter * painter, const QStyleOptionViewItem & options, const QModelIndex & index) const
{
	QTreeView::drawRow(painter, options, index);

	//繪制網格線
	QPen pen;
	pen.setWidth(m_iWidth);
	pen.setColor(m_gridLineColor);

	painter->save();
	painter->setPen(pen);
	painter->drawRect(options.rect);
	painter->restore();
}

b、繪制branch

繪制行函數主要是添加了單元格邊框繪制,接下來就是第一列的branch繪制。

繪制branch時一定不要忘記調用原有的繪制函數,否則界面顯示會異常。

{
	painter->save();
	QTreeView::drawBranches(painter, rect, index);
	painter->restore();
}

繪制branch時主要是根據當前節點是否展開、是否有孩子節點、是否有兄弟節點等狀態來聯合判斷並進行繪制

如下是繪制代碼,可能有些長,但是應該比較好理解。

需要注意的點

  1. 除根節點外,每個節點都需要繪制文字前邊的水平線
  2. 有父親的節點需要繪制垂直線。繪制的豎線是否繪制到底,取決於是否有向下的兄弟
  3. 有爺爺的節點可能需要額外繪制向下的豎線。是否繪制取決於自己的父親是否有向下的兄弟
  4. 規則3其實是一個循環的處理,也就是說爺爺如果有爸爸,也就是說節點如果有祖爺爺,那么可能還需要繪制更多的向下豎線。是否繪制取決於節點的爺爺是否有向下的兄弟

代碼這里就不細說了,有興趣的可以自己研究研究。繪制規則就是上述4點

//繪制branch
{
	DataNode * node = static_cast<DataNode *>(index.internalPointer());
	bool hasChild = node->children().size() != 0;//是否有孩子

	QList<DataNode *> & children = node->parent()->children();
	bool has_next_siblings = children.indexOf(node) != (children.size() - 1);//是否有向后的兄弟
	bool has_pre_siblings = children.indexOf(node) != 0;//是否有向前的兄弟

	int level = node->level();
	int indentaion = indentation();//縮進
	int indentaions = indentaion * (level - 1);//縮進距離

	QRect r = rect;
	r.setLeft(r.left() + indentaions);//圖標繪制位置

	painter->save();
	painter->setPen(m_branchLine);

	bool expaned = isExpanded(index);//節點是否展開

	QLine line(r.center() + QPoint(0, r.top() - r.center().y()), r.center() + QPoint(0, r.bottom() - r.center().y()));
	line.translate(-indentaion, 0);
	//QLine line(r.topLeft(), r.bottomLeft());
	//循環繪制(具有兄弟節點的)父節點向下的豎線
	DataNode * parent_node = node->parent();
	DataNode * sub_node = node;
	bool isNeed = node->children().size() == 0;
	for (int i = level - 1; i >= 0; --i)
	{
		QList<DataNode *> & children = parent_node->children();
		bool has_next_siblings = children.indexOf(sub_node) != (children.size() - 1);//父節點是否有(向后的)兄弟

		if (has_next_siblings)
		{
			painter->drawLine(line);
		}
		

		if (level - 1 == i)
		{
			QPoint pos = (line.p1() + line.p2()) / 2;
			QPoint pos2 = pos + QPoint(indentaion / 2, 0);

			painter->drawLine(pos, pos2);

			if (!has_next_siblings)
			{
				painter->drawLine(line.p1(), (line.p1() + line.p2()) / 2);
			}
		}

		sub_node = parent_node;
		parent_node = parent_node->parent();
		line.translate(-indentaion, 0);
	}

	QPixmap pix;
	if (expaned)
	{
		if (hasChild)
		{
			pix = QPixmap(":/branch-expand.png");
		}
	}
	else
	{
		if (hasChild)
		{
			pix = QPixmap(":/branch-collapse.png");
		}
	}
	if (pix.isNull() == false)
	{
		QRect pixRect = QRect(QPoint(0, 0), pix.size());
		pixRect.moveCenter(r.center());

		if (expaned)
		{
			QLine line(r.center(), r.center() + QPoint(0, r.bottom() - r.center().y()));
			painter->drawLine(line);
		}

		painter->drawPixmap(pixRect, pix);
	}

	painter->restore();
}

3、同步左側表頭

上一篇文章Qt實現表格樹控件-支持多級表頭 中已經說了,我們的表格控件是使用QTableView+QTreeView來實現的,那么我們操作樹控件時必然要對表格中的表頭進行同步操作了。

樹控件折疊時隱藏垂直表頭指定行

void collapsed_p(DataNode * node)
{
	QList<DataNode *> childNodeList = node->children();
	//DataManager::getInstance()->allChildNode(node, childNodeList);

	int size = childNodeList.size();
	for (int i = 0; i < size; ++i)
	{
		int serial = DataManager::getInstance()->serialNoOfNode(childNodeList.at(i));
		VHeaderView::instance->SetRowHide(serial, true);

		QModelIndex subIndex = FrozenTreeView::instance->rowIndex(serial);

		collapsed_p(childNodeList.at(i));
	}
}

void FrozenTreeView::onCollapsed(const QModelIndex & index)
{
	if (!index.isValid())
		return;

	DataNode * node = static_cast<DataNode*>(index.internalPointer());
	if (nullptr == node)
		return;

	collapsed_p(node);
	VHeaderView::instance->UpdateCache();

	//要對水平頭的最后一列進行重設大小,引起水平頭自己的更新操作,從而使整個界面顯示正確
	HHeaderView::instance->resizeLastSection(true);
}

樹控件展開時顯示垂直表頭指定行

void expanded_p(DataNode * node)
{
	QList<DataNode *> childNodeList = node->children();

	int size = childNodeList.size();
	for (int i = 0; i < size; ++i)
	{
		int serial = DataManager::getInstance()->serialNoOfNode(childNodeList.at(i));
		VHeaderView::instance->SetRowHide(serial, false);

		QModelIndex subIndex = FrozenTreeView::instance->rowIndex(serial);

		if (FrozenTreeView::instance->isExpanded(subIndex))
		{
			expanded_p(childNodeList.at(i));
		}
	}
}

void FrozenTreeView::onExpanded(const QModelIndex & index)
{
	DataNode * node = static_cast<DataNode *>(index.internalPointer());
	if (nullptr == node)
		return;

	VHeaderView::instance->blockSignals(true);
	expanded_p(node);
	VHeaderView::instance->UpdateCache();
	VHeaderView::instance->blockSignals(false);

	//要對水平頭的最后一列進行重設大小,引起水平頭自己的更新操作,從而使整個界面顯示正確
	HHeaderView::instance->resizeLastSection(false);
}

五、相關文章


值得一看的優秀文章:

  1. 財聯社-產品展示
  2. 廣聯達-產品展示
  3. Qt定制控件列表
  4. 牛逼哄哄的Qt庫

  1. Qt實現表格控件-支持多級列表頭、多級行表頭、單元格合並、字體設置等

  2. Qt高仿Excel表格組件-支持凍結列、凍結行、內容自適應和合並單元格

  3. 屬性瀏覽器控件QtTreePropertyBrowser編譯成動態庫(設計師插件)

  4. 超級實用的屬性瀏覽器控件--QtTreePropertyBrowser

  5. Qt之表格控件螞蟻線

  6. QRowTable表格控件-支持hover整行、checked整行、指定列排序等

  7. Qt實現表格樹控件-支持多級表頭

  8. QTableView表格控件區域選擇-自繪選擇區域


如果您覺得文章不錯,不妨給個 打賞,寫作不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!!




很重要--轉載聲明

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords

  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。



免責聲明!

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



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