[TOC] 原文鏈接: "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類的幫助文檔,查找這個類都有哪些可供重寫的介面,然後就發現了這麼幾個函數
看名字大概都知道是什麼意思,不過這裡還是做簡要說明
前邊提到我們要自己繪製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時主要是根據當前節點是否展開、是否有孩子節點、是否有兄弟節點等狀態來聯合判斷併進行繪製
如下是繪製代碼,可能有些長,但是應該比較好理解。
需要註意的點
- 除根節點外,每個節點都需要繪製文字前邊的水平線
- 有父親的節點需要繪製垂直線。繪製的豎線是否繪製到底,取決於是否有向下的兄弟
- 有爺爺的節點可能需要額外繪製向下的豎線。是否繪製取決於自己的父親是否有向下的兄弟
- 規則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);
}
五、相關文章
值得一看的優秀文章:
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!!
很重要--轉載聲明
本站文章無特別說明,皆為原創,版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。