在使用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)
。
槽函数比较简单,就是对上面两个函数的调用。