原文連接:Qt實現表格樹控件-自繪樹節點虛線node
一程序員第一次上女友家她媽板着臉問 :你想娶我女兒,有多少存款?程序員
程序員低了下頭:五百!瀏覽器
她媽更鄙視了:才五百塊,買個廁所都不夠!ide
程序員忙說:不是人民幣!函數
她媽:就算是美圓,仍是不夠買廁所!字體
程序員:實際上是比特幣!url
她媽:哇,賢婿,我給你買只大龍蝦去spa
自繪樹節點?聽起來都挺複雜的,但是爲何還要自繪樹節點呢?這充分說明產品的腦子是什麼東西都能想出來的。.net
有一天產品說咱們的軟件裏缺乏一個美麗的樹控件,而後就要求開發去實現這個功能。
對於有必定開發經驗的同窗可能直接會去百度,或者上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的源碼。
首先咱們打開QTreeView類的幫助文檔,查找這個類都有哪些可供重寫的接口,而後就發現了這麼幾個函數
看名字大概都知道是什麼意思,不過這裏仍是作簡要說明
前邊提到咱們要本身繪製branch線條,可是其他的東西仍是要走Qt默認的繪製風格,所以在重寫繪製函數時,千萬不要忘記了調用原有的繪製方法。
表格中前3個函數就是繪製樹控件的具體方法,這3個函數搭配起來完成了樹控件內容格子的繪製。下面咱們來重寫這3個函數,分別完成咱們的需求
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時主要是根據當前節點是否展開、是否有孩子節點、是否有兄弟節點等狀態來聯合判斷並進行繪製
以下是繪製代碼,可能有些長,可是應該比較好理解。
須要注意的點
代碼這裏就不細說了,有興趣的能夠本身研究研究。繪製規則就是上述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(); }
上一篇文章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); }
值得一看的優秀文章:
![]() |
![]() |
很重要--轉載聲明