工作中经常遇到树形结构且需要设置其勾选状态,状态包括选中,半选,取消选择三种状态
1.当选中子节点时:
- 子节点的处理
判断若选中的节点不是半选中状态,则判断子节点与父节点状态是否一致,不一致的则设置一致
若该节点为半选中状态,则其父节点设置为半选中状态 - 父节点处理
先得到父节点所有的孩子节点,判断子节点勾选的总数selectCount
selectCount = childrenCount;则该父节点需要设置为选中状态
selectCount = 0 ;则该父节点需要设置为未选中状态
否则设置为半选中状态
在QTreeWidget中树结构的每一项都是一个QTreeWidgetItem,通过对它的设置可以修改树的显示效果,对于树的三态切换需要处理QTreeWidget中的一个信号:
当某一个节点被选中或者取消选中的时候需要处理以下情况:
- 处理该节点的子节点(如果它有子节点),它的子节点的状态(Check或者Uncheck)和它一样 (如果它的子节点中有目录,那么还需要递归处理子节点的子节点)
- 处理该节点的父节点,父节点会根据当前它子节点的状态来调整自身的状态(如果该父节点还有父节点,那么还需要递归处理父节点的父节点)
在QTreeWidget中的itemChanged事件会一直递归的调用,也就是说如果我们设置了子节点的状态(使用程序设置,或者手动点击),那么被设置的节点会继续调用itemChanged信号,根据这个特点,我们在编写代码的过程中不需要考虑递归的问题,只需要设置一个层级的处理即可。
效果图
sample:
treewidgettest.cpp
#include "treewidgettest.h"
TreeWidgetTest::TreeWidgetTest(QWidget *parent)
: QTreeWidget(parent)
{
ui.setupUi(this);
InitData();
expandAll();
setHeaderHidden(true);
connect(this, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(ItemChangedSlot(QTreeWidgetItem *, int)));
}
void TreeWidgetTest::InitData()
{
QTreeWidgetItem* topLevelDirectory = new QTreeWidgetItem();
topLevelDirectory->setText(0, "China");
topLevelDirectory->setCheckState(0, Qt::Checked);
topLevelDirectory->setIcon(0, QIcon(":/TreeWidgetTest/folder.jpeg"));
QTreeWidgetItem* directoryC = new QTreeWidgetItem();
directoryC->setText(0, "AnHui");
directoryC->setCheckState(0, Qt::Checked);
directoryC->setIcon(0, QIcon(":/TreeWidgetTest/folder.jpeg"));
QTreeWidgetItem* file1 = new QTreeWidgetItem();
file1->setText(0, "HeFei");
file1->setCheckState(0, Qt::Checked);
file1->setIcon(0, QIcon(":/TreeWidgetTest/file.jpeg"));
QTreeWidgetItem* file2 = new QTreeWidgetItem();
file2->setText(0, "Liuan");
file2->setCheckState(0, Qt::Checked);
file2->setIcon(0, QIcon(":/TreeWidgetTest/file.jpeg"));
directoryC->addChild(file1);
directoryC->addChild(file2);
QTreeWidgetItem* directoryD = new QTreeWidgetItem();
directoryD->setText(0, "HeNan");
directoryD->setCheckState(0, Qt::Checked);
directoryD->setIcon(0, QIcon(":/TreeWidgetTest/folder.jpeg"));
QTreeWidgetItem* file3 = new QTreeWidgetItem();
file3->setText(0, "ZhengZhou");
file3->setCheckState(0, Qt::Checked);
file3->setIcon(0, QIcon(":/TreeWidgetTest/file.jpeg"));
directoryD->addChild(file3);
QTreeWidgetItem* directoryE = new QTreeWidgetItem();
directoryE->setText(0, "JIANG_SU");
directoryE->setCheckState(0, Qt::Checked);
directoryE->setIcon(0, QIcon(":/TreeWidgetTest/folder.jpeg"));
QTreeWidgetItem* file4 = new QTreeWidgetItem();
file4->setText(0, "SuZhou");
file4->setCheckState(0, Qt::Checked);
file4->setIcon(0, QIcon(":/TreeWidgetTest/file.jpeg"));
QTreeWidgetItem* file5 = new QTreeWidgetItem();
file5->setText(0, "WuXi");
file5->setCheckState(0, Qt::Checked);
file5->setIcon(0, QIcon(":/TreeWidgetTest/file.jpeg"));
QTreeWidgetItem* direcotryE1 = new QTreeWidgetItem();
direcotryE1->setText(0, "Nanjing");
direcotryE1->setCheckState(0, Qt::Checked);
direcotryE1->setIcon(0, QIcon(":/TreeWidgetTest/folder.jpeg"));
QTreeWidgetItem* file6 = new QTreeWidgetItem();
file6->setText(0, "JiangbeiXinqu");
file6->setCheckState(0, Qt::Checked);
file6->setIcon(0, QIcon(":/TreeWidgetTest/file.jpeg"));
direcotryE1->addChild(file6);
directoryE->addChild(file4);
directoryE->addChild(file5);
directoryE->addChild(direcotryE1);
QList<QTreeWidgetItem*> topLevelItemList;
topLevelItemList << directoryC << directoryD << directoryE;
topLevelDirectory->addChildren(topLevelItemList);
addTopLevelItem(topLevelDirectory);
}
void TreeWidgetTest::SetChildCheckState(QTreeWidgetItem* item, Qt::CheckState cs)
{
if (!item) return;
for (int i = 0; i < item->childCount(); i++)
{
QTreeWidgetItem* child = item->child(i);
// 这里避免重复设置的问题
if (child->checkState(0) != cs)
{
child->setCheckState(0, cs);
}
}
SetParentCheckState(item->parent());
}
void TreeWidgetTest::SetParentCheckState(QTreeWidgetItem* item)
{
if (!item) return;
int selectedCount = 0;
int childCount = item->childCount();
for (int i = 0; i < childCount; i++)
{
QTreeWidgetItem* child = item->child(i);
if (child->checkState(0) == Qt::Checked)
{
selectedCount++;
}
}
if (selectedCount == 0) {
item->setCheckState(0, Qt::Unchecked);
}
else if (selectedCount == childCount) {
item->setCheckState(0, Qt::Checked);
}
else {
item->setCheckState(0, Qt::PartiallyChecked);
}
}
bool TreeWidgetTest::IsTopItem(QTreeWidgetItem* item)
{
if (!item)
return false;
if (!item->parent())
return true;
return false;
}
void TreeWidgetTest::ItemChangedSlot(QTreeWidgetItem* item, int column)
{
if (Qt::PartiallyChecked != item->checkState(0))
SetChildCheckState(item, item->checkState(0));
// 此种情况不是人工点击的,而是子项设置的父节点才会这种状态
if (Qt::PartiallyChecked == item->checkState(0))
if (!IsTopItem(item))
item->parent()->setCheckState(0, Qt::PartiallyChecked);
}
头文件treewidgettest.h
#pragma once
#include <QtWidgets/QWidget>
#include <QTreeWidget>
#include "ui_treewidgettest.h"
class TreeWidgetTest : public QTreeWidget
{
Q_OBJECT
public:
TreeWidgetTest(QWidget *parent = Q_NULLPTR);
void InitData();
void SetChildCheckState(QTreeWidgetItem* item, Qt::CheckState cs);
void SetParentCheckState(QTreeWidgetItem* item);
bool IsTopItem(QTreeWidgetItem* item);
private:
Ui::TreeWidgetTestClass ui;
public slots:
void ItemChangedSlot(QTreeWidgetItem* item, int column);
};
main.cpp
#include "treewidgettest.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TreeWidgetTest w;
w.show();
return a.exec();
}