在使用QTreeWidget顯示文件樹時,需要對樹的節點做一些功能的限制:
- 勾選某一節點時,該節點的子項自動全部選中
- 子項部分勾選時,父節點狀態爲部分勾選
- 子項全部勾選時,父節點自動設置勾選
首先,查看了Qt文檔,發現竟然沒有提供這個功能,所以自己寫了一個簡單的例子。
先看效果:
QTreeWidget在添加節點時,其節點前面的複選框默認時不顯示的,而要顯示覆選框,我們則需要通過QTreeWidgetItem的方法來設置。
void QTreeWidgetItem::setCheckState(int column, Qt::CheckState state)
Sets the item in the given column check state to be state.
Qt::CheckState是一個枚舉的狀態,主要狀態有下面三種。
Constant | Value | Description |
---|---|---|
Qt::Unchecked | 0 | The item is unchecked. |
Qt::PartiallyChecked | 1 | The item is partially checked. Items in hierarchical models may be partially checked if some, but not all, of their children are checked. |
Qt::Checked | 2 | The item is checked. |
簡單的來,我們以三層的一個樹來說明下。
template<typename T>
QTreeWidgetItem* TreeWidget::addChild(T parent, const QString& text)
{
auto item = new QTreeWidgetItem(parent, QStringList(text));
item->setCheckState(0, Qt::Unchecked);
return item;
}
首先,我們先以模版函數的形式來寫一個添加樹節點的函數,這樣會免去我們後面在初始化樹的時候會手忙腳亂。
如下,我們首先初始化一棵樹,這棵樹有三級。
void TreeWidget::initTree()
{
auto item = addChild(ui->tree, "top");
QList<QTreeWidgetItem*> listChild;
for(int nIndex = 1; nIndex <= 5; ++nIndex)
{
auto pItem = addChild(item, QString("child%1").arg(nIndex));
listChild.append(pItem);
}
for(const auto& child : listChild)
{
for(int nRet = 1; nRet <= 5; ++nRet)
{
addChild(child, QString("grandchild%1").arg(nRet));
}
}
ui->tree->expandAll();
}
接下來就是怎樣在改變一個節點的選中狀態後,怎麼更改其餘節點的選中狀態,我們首先看一看勾選之後怎麼設置父節點的選中狀態。
想一想:改變了一個節點的狀態,那麼對於它的父節點來說,有一個孩子的狀態改變了,會有幾種情況:
- 它所有的子節點全都是選中的狀態
- 它的子節點有部分是選中的,部分是未選中的
- 它所有的子節點均未選中
有了上面的分析,那麼我們設置這個父節點的狀態時是不是需要遍歷一次它的直接子節點,通過它的直接子節點的狀態來設置父節點的狀態。
那麼,以此類推,父節點的父節點呢?
不難發現,每上一個層級,我們都在重複前面的操作,因此,可以使用遞歸的方法來設置父節點的狀態。
void TreeWidget::updateParentItemStatus(QTreeWidgetItem* item)
{
auto parent = item->parent();
if (Q_NULLPTR == parent)
{
return;
}
//先把父節點的狀態設置爲改變的子節點的狀態
parent->setCheckState(0, item->checkState(0));
//然後遍歷它的子節點,如果有節點的狀態和父節點的狀態不一致,則是部分選中,否則全爲選中或者未選中
int nCount = parent->childCount();
for (int nIndex = 0; nIndex < nCount; ++nIndex)
{
auto child = parent->child(nIndex);
if (child->checkState(0) != parent->checkState(0))
{
parent->setCheckState(0, Qt::PartiallyChecked);
break;
}
}
//設置該父節點的父節點,直到根節點
updateParentItemStatus(parent);
}
既然,父節點是這樣的,那麼,子節點的設置呢?是不是也是一樣的呢?
仔細想一想,方式也是差不多的,設置它的子節點,就是對它的所有的孩子(包括直系和不是直系)設置爲和它一樣的狀態,因此,如下:
void TreeWidget::updateChildItemStatus(QTreeWidgetItem* item)
{
int nCount = item->childCount();
for (int nIndex = 0; nIndex < nCount; ++nIndex)
{
auto child = item->child(nIndex);
child->setCheckState(0, item->checkState(0));
if (child->childCount() > 0)
{
updateChildItemStatus(child);
}
}
}
最後,我們需要一個信號來進行設置狀態,信號由很多種,但是選擇的時候需要注意,可能會存在一些坑,比如使用itemChanged(QTreeWidget*, int)
會有一些意外的收穫,有什麼收穫,會在後面的博客中說明。這邊我選擇的是itemClicked(QTreeWidget*, int)
。
槽函數比較簡單,就是對上面兩個函數的調用。