实现 QTreeWidget 中子节点勾选状态的递归更新功能只影响跟节点的状态父节点状态不受影响

发布于:2025-02-11 ⋅ 阅读:(76) ⋅ 点赞:(0)

在 Qt 开发中,QTreeWidget 提供了树形结构的显示和交互功能。为了实现某个子节点勾选或取消勾选时,只影响当前节点及其子节点的状态,同时递归更新父节点的状态以正确显示 Qt::PartiallyChecked 或 Qt::Checked,我们可以借助 Qt 的信号与槽机制进行处理。以下是详细实现及测试分析

1.功能需求分析

我们希望实现如下功能:

1.1勾选或取消勾选某个子节点时:

仅更新当前节点及其子节点状态。
递归更新最顶层父节点状态(Qt::Checked 或 Qt::PartiallyChecked)。

1.2不影响兄弟节点及父节点的兄弟节点状态。

测试场景如下:
场景 1:部分勾选子节点
根节点 A
子节点 A1 [Checked]
子节点 A2 [Unchecked]
结果:根节点 A 的状态为 Qt::PartiallyChecked。
场景 2:全部勾选子节点
根节点 B
子节点 B1 [Checked]
子节点 B2 [Checked]
结果:根节点 B 的状态为 Qt::Checked。
场景 3:多层嵌套
根节点 C
子节点 C1
子节点 C1.1 [Checked]
子节点 C1.2 [Unchecked]
子节点 C2 [Checked]
结果:子节点 C1 的状态为 Qt::PartiallyChecked,根节点 C 的状态为 Qt::PartiallyChecked。

2 效果展示

在这里插入图片描述

3.实现步骤

3.1 main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

3.2 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTreeWidget>

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    QTreeWidget *treeWidget;
    void setupTree();
    // checkedCount  子节点中已勾选的数量
    void updateParentState(QTreeWidgetItem *item,int checkedCount = 0);
    void updateChildState(QTreeWidgetItem *item, Qt::CheckState state);
    // 使用 统计节点数量
    int countDescendants(QTreeWidgetItem *node);
    //获取当前节点下的勾选状态
    bool areAllDescendantsChecked(QTreeWidgetItem *node);
private slots:
    void onItemChanged(QTreeWidgetItem *item, int column);
};

#endif // MAINWINDOW_H

3.3 mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), treeWidget(new QTreeWidget(this)) {
    setCentralWidget(treeWidget);
    setupTree();

    // 连接信号槽
    connect(treeWidget, &QTreeWidget::itemChanged, this, &MainWindow::onItemChanged);
}

MainWindow::~MainWindow() {}

void MainWindow::setupTree() {
    treeWidget->setColumnCount(1);
    treeWidget->setHeaderLabel("Tree Example");

    // 添加根节点 A,及其子节点
    QTreeWidgetItem *rootA = new QTreeWidgetItem(treeWidget, QStringList("A"));
    rootA->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childA1 = new QTreeWidgetItem(rootA, QStringList("A1"));
    childA1->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childA2 = new QTreeWidgetItem(rootA, QStringList("A2"));
    childA2->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childA3 = new QTreeWidgetItem(childA2, QStringList("A2.1"));
    childA3->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childA4 = new QTreeWidgetItem(childA2, QStringList("A2.2"));
    childA4->setCheckState(0, Qt::Unchecked);

    // 添加根节点 B,及其子节点
    QTreeWidgetItem *rootB = new QTreeWidgetItem(treeWidget, QStringList("B"));
    rootB->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childB1 = new QTreeWidgetItem(rootB, QStringList("B1"));
    childB1->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childB2 = new QTreeWidgetItem(rootB, QStringList("B2"));
    childB2->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childB3 = new QTreeWidgetItem(childB2, QStringList("B2.1"));
    childB3->setCheckState(0, Qt::Unchecked);

    // 添加根节点 C,及其多层子节点
    QTreeWidgetItem *rootC = new QTreeWidgetItem(treeWidget, QStringList("C"));
    rootC->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childC1 = new QTreeWidgetItem(rootC, QStringList("C1"));
    childC1->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childC2 = new QTreeWidgetItem(childC1, QStringList("C1.1"));
    childC2->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childC3 = new QTreeWidgetItem(childC1, QStringList("C1.2"));
    childC3->setCheckState(0, Qt::Unchecked);
    QTreeWidgetItem *childC31 = new QTreeWidgetItem(childC1, QStringList("C1.3"));
    childC31->setCheckState(0, Qt::Unchecked);

    QTreeWidgetItem *childC4 = new QTreeWidgetItem(rootC, QStringList("C2"));
    childC4->setCheckState(0, Qt::Unchecked);

    // 添加根节点 D,及其子节点
    QTreeWidgetItem *rootD = new QTreeWidgetItem(treeWidget, QStringList("D"));
    rootD->setCheckState(0, Qt::Unchecked);
    QTreeWidgetItem *childD1 = new QTreeWidgetItem(rootD, QStringList("C1"));
    childD1->setCheckState(0, Qt::Unchecked);
}

void MainWindow::onItemChanged(QTreeWidgetItem *item, int column) {
    if (column != 0) return;
    treeWidget->blockSignals(true);// 防止递归调用 信号屏蔽
    Qt::CheckState state = item->checkState(0);// 更新子节点状态
    updateChildState(item, state);
    updateParentState(item);// 更新父节点状态
    treeWidget->blockSignals(false);// 恢复信号
}

void MainWindow::updateChildState(QTreeWidgetItem *item, Qt::CheckState state) {
    for (int i = 0; i < item->childCount(); ++i) {
        QTreeWidgetItem *child = item->child(i);
        child->setCheckState(0, state);
        updateChildState(child, state);// 递归处理子节点
    }
}

int MainWindow::countDescendants(QTreeWidgetItem *node)
{
    if (!node) return 0;
        int count = 0;
        // 遍历直接子节点
            for (int i = 0; i < node->childCount(); ++i) {
                QTreeWidgetItem *child = node->child(i);
                ++count; // 当前子节点计数
                count += countDescendants(child); // 递归统计子节点的子节点
            }
            return count;
}

bool MainWindow::areAllDescendantsChecked(QTreeWidgetItem *node)
{
    if (!node) return false;
        // 遍历子节点
        for (int i = 0; i < node->childCount(); ++i) {
            // 检查当前节点的状态
             QTreeWidgetItem *child = node->child(i);
            QString Text = child->text(0);
            if (child->checkState(0) != Qt::Checked) {
                return false;
            }
            if (!areAllDescendantsChecked(node->child(i))) {
                return false;
            }
        }

        return true;
}

void MainWindow::updateParentState(QTreeWidgetItem *item,int checkedCount) {
    QTreeWidgetItem *parent = item->parent();
    if (!parent) return;

    int childCheckedCount = checkedCount;
    int partiallyCheckedCount = 0; // 子节点中部分勾选的数量
    int parentCheckedCount = 0; //检验根节点选择个数(只针对根节点有用)
    // 遍历所有子节点,统计状态
    for (int i = 0; i < parent->childCount(); ++i) {
        QTreeWidgetItem *child = parent->child(i);
        // 递归更新父节点
        QString text = child->text(0);
        if (child->checkState(0) == Qt::Checked) {
            ++checkedCount;
            if(parent->parent() == nullptr)
            {
                ++parentCheckedCount;
            }
        } else if (child->checkState(0) == Qt::PartiallyChecked) {
            ++partiallyCheckedCount;
        }
    }

    // 根据子节点状态更新 根节点状态
    if(parent->parent() == nullptr )
    {
    	// (checkedCount > parent->childCount() && (checkedCount - childCheckedCount) == parent->childCount()) 多层节点后 取消勾选情景
        if (checkedCount == parent->childCount() ||  (checkedCount > parent->childCount() && (checkedCount - childCheckedCount) == parent->childCount()) ) {//第二层
            if(parentCheckedCount == parent->childCount())
            {
                //分情况考虑 只考虑第二层节点
                bool status = false;
                for (int i = 0; i < parent->childCount(); ++i) {
                     QTreeWidgetItem *child = parent->child(i);
                     status = areAllDescendantsChecked(child);
                     if(!status)
                     {
                         break;
                     }
                }
                if(status)
                {
                     parent->setCheckState(0, Qt::Checked);
                }
                else
                {
                     parent->setCheckState(0, Qt::PartiallyChecked);
                }

            }
            else if(checkedCount != 0)
            {
                parent->setCheckState(0, Qt::PartiallyChecked);
            }
            else
            {

            }
        }
        else if (checkedCount > 0 || partiallyCheckedCount > 0) {
            parent->setCheckState(0, Qt::PartiallyChecked);
        } else {
            parent->setCheckState(0, Qt::Unchecked);
        }
    }
    // 递归更新父节点
    updateParentState(parent,checkedCount);
}

}

注意点

点击勾选前面的选择框 有时不能设置currentItem
不能通过 currentitem 来获取当前勾选的状态

在实现 if(parent->parent() == nullptr ) 这一功能时 原来的思想是 只需要判断第二层勾选状态就行 但忽略了在多层勾选完再取消
进入不了这一判断条件
递归是最终状态


网站公告

今日签到

点亮在社区的每一天
去签到