PyQt(12)TreeWidget与TreeView对比

1.TreeWidget实现树形列表 勾选与右键菜单

python 复制代码
import sys
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QTreeWidget, QTreeWidgetItem,
    QMenu, QAction, QMessageBox
)
from PyQt5.QtCore import Qt


class TreeWidgetDemo(QMainWindow):
    def __init__(self):
        super().__init__()
        self._is_updating = False  # 防递归死循环标志
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("QTreeWidget 勾选联动示例")
        self.setGeometry(100, 100, 600, 400)

        # 1. 创建 TreeWidget
        self.tree_widget = QTreeWidget(self)
        self.setCentralWidget(self.tree_widget)
        self.tree_widget.setColumnCount(1)
        self.tree_widget.setHeaderLabel("文件目录")

        # 2. 启用节点勾选功能(支持半选状态)
        self.tree_widget.setSelectionMode(QTreeWidget.ExtendedSelection)
        self.tree_widget.setItemsExpandable(True)
        self.tree_widget.setExpandsOnDoubleClick(True)

        # 3. 构建树形节点(带勾选框,支持半选)
        self._create_tree_items()

        # 4. 绑定右键菜单和勾选变化事件
        self.tree_widget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_widget.customContextMenuRequested.connect(self.show_right_menu)
        self.tree_widget.itemChanged.connect(self.on_item_check_changed)

    def _create_tree_items(self):
        """创建树形节点,启用勾选框(支持半选)"""
        # 根节点1:文档
        root1 = QTreeWidgetItem(self.tree_widget, ["文档"])
        # 启用勾选+半选功能
        root1.setFlags(root1.flags() | Qt.ItemIsUserCheckable)
        root1.setCheckState(0, Qt.Unchecked)

        # 子节点1-1/1-2
        child1_1 = QTreeWidgetItem(root1, ["简历.docx"])
        child1_1.setFlags(child1_1.flags() | Qt.ItemIsUserCheckable)
        child1_1.setCheckState(0, Qt.Unchecked)

        child1_2 = QTreeWidgetItem(root1, ["报告.pdf"])
        child1_2.setFlags(child1_2.flags() | Qt.ItemIsUserCheckable)
        child1_2.setCheckState(0, Qt.Unchecked)

        # 根节点2:图片(多级子节点示例)
        root2 = QTreeWidgetItem(self.tree_widget, ["图片"])
        root2.setFlags(root2.flags() | Qt.ItemIsUserCheckable)
        root2.setCheckState(0, Qt.Unchecked)

        # 二级节点:风景
        child2_1 = QTreeWidgetItem(root2, ["风景"])
        child2_1.setFlags(child2_1.flags() | Qt.ItemIsUserCheckable)
        child2_1.setCheckState(0, Qt.Unchecked)

        # 三级节点:山川/湖泊
        child2_1_1 = QTreeWidgetItem(child2_1, ["山川.jpg"])
        child2_1_1.setFlags(child2_1_1.flags() | Qt.ItemIsUserCheckable)
        child2_1_1.setCheckState(0, Qt.Unchecked)

        child2_1_2 = QTreeWidgetItem(child2_1, ["湖泊.jpg"])
        child2_1_2.setFlags(child2_1_2.flags() | Qt.ItemIsUserCheckable)
        child2_1_2.setCheckState(0, Qt.Unchecked)

        self.tree_widget.expandAll()

    def on_item_check_changed(self, item, column):
        """勾选状态变化联动处理(核心逻辑)"""
        if self._is_updating or column != 0:  # 避免递归死循环/只处理第0列
            return

        self._is_updating = True  # 锁定更新
        try:
            current_state = item.checkState(0)
            # 1. 父节点变化:同步所有子节点
            self._set_child_check_state(item, current_state)
            # 2. 子节点变化:更新所有父节点(半选/全选/未选)
            self._update_parent_check_state(item.parent())
        finally:
            self._is_updating = False  # 解锁更新

    def _set_child_check_state(self, parent_item, check_state):
        """递归设置父节点下所有子节点的勾选状态"""
        child_count = parent_item.childCount()
        for i in range(child_count):
            child = parent_item.child(i)
            child.setCheckState(0, check_state)
            self._set_child_check_state(child, check_state)  # 递归处理孙子节点

    def _update_parent_check_state(self, parent_item):
        """递归更新父节点的勾选状态(全选/半选/未选)"""
        if not parent_item:  # 无父节点则终止
            return

        child_count = parent_item.childCount()
        checked_count = 0
        unchecked_count = 0

        # 统计子节点勾选状态
        for i in range(child_count):
            child = parent_item.child(i)
            state = child.checkState(0)
            if state == Qt.Checked:
                checked_count += 1
            elif state == Qt.Unchecked:
                unchecked_count += 1

        # 更新父节点状态
        if checked_count == child_count:
            parent_item.setCheckState(0, Qt.Checked)  # 全选
        elif unchecked_count == child_count:
            parent_item.setCheckState(0, Qt.Unchecked)  # 未选
        else:
            parent_item.setCheckState(0, Qt.PartiallyChecked)  # 半选

        # 递归更新上层父节点
        self._update_parent_check_state(parent_item.parent())

    # 以下为原有右键菜单等功能(无修改)
    def show_right_menu(self, pos):
        current_item = self.tree_widget.itemAt(pos)
        if not current_item:
            return

        menu = QMenu(self.tree_widget)
        info_action = QAction("查看节点信息", self)
        info_action.triggered.connect(lambda: self.show_item_info(current_item))
        menu.addAction(info_action)

        toggle_action = QAction("勾选/取消勾选", self)
        toggle_action.triggered.connect(lambda: self.toggle_item_check(current_item))
        menu.addAction(toggle_action)

        menu.addSeparator()
        del_action = QAction("删除节点", self)
        del_action.triggered.connect(lambda: self.delete_item(current_item))
        menu.addAction(del_action)

        menu.exec_(self.tree_widget.mapToGlobal(pos))

    def show_item_info(self, item):
        state_map = {
            Qt.Unchecked: "未勾选",
            Qt.PartiallyChecked: "半选",
            Qt.Checked: "已勾选"
        }
        check_state = state_map.get(item.checkState(0), "未知")
        QMessageBox.information(
            self, "节点信息",
            f"名称:{item.text(0)}\n状态:{check_state}"
        )

    def toggle_item_check(self, item):
        current_state = item.checkState(0)
        new_state = Qt.Unchecked if current_state == Qt.Checked else Qt.Checked
        item.setCheckState(0, new_state)

    def delete_item(self, item):
        if item.parent() is None:
            index = self.tree_widget.indexOfTopLevelItem(item)
            self.tree_widget.takeTopLevelItem(index)
        else:
            item.parent().removeChild(item)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = TreeWidgetDemo()
    window.show()
    sys.exit(app.exec_())

2.TreeView实现树形列表 勾选与右键菜单

python 复制代码
import sys
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QTreeView, QMenu, QAction,
    QMessageBox, QAbstractItemView
)
from PyQt5.QtCore import Qt, QModelIndex
from PyQt5.QtGui import QStandardItemModel, QStandardItem


class TreeViewDemo(QMainWindow):
    def __init__(self):
        super().__init__()
        self._is_updating = False  # 防递归死循环标志
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("QTreeView 勾选联动示例")
        self.setGeometry(100, 100, 600, 400)

        # 1. 创建模型
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(["文件目录"])

        # 2. 创建 TreeView
        self.tree_view = QTreeView(self)
        self.setCentralWidget(self.tree_view)
        self.tree_view.setModel(self.model)
        self.tree_view.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.tree_view.setExpandsOnDoubleClick(True)
        self.tree_view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view.customContextMenuRequested.connect(self.show_right_menu)

        # 3. 构建模型节点
        self._create_model_items()

        # 4. 监听模型数据变化
        self.model.dataChanged.connect(self.on_model_data_changed)
        self.tree_view.expandAll()

    def _create_model_items(self):
        """创建带勾选联动的节点"""
        # 根节点1:文档
        root1 = QStandardItem("文档")
        root1.setCheckable(True)
        root1.setCheckState(Qt.Unchecked)
        self.model.appendRow(root1)

        # 子节点1-1/1-2
        child1_1 = QStandardItem("简历.docx")
        child1_1.setCheckable(True)
        child1_1.setCheckState(Qt.Unchecked)
        root1.appendRow(child1_1)

        child1_2 = QStandardItem("报告.pdf")
        child1_2.setCheckable(True)
        child1_2.setCheckState(Qt.Unchecked)
        root1.appendRow(child1_2)

        # 根节点2:图片(多级示例)
        root2 = QStandardItem("图片")
        root2.setCheckable(True)
        root2.setCheckState(Qt.Unchecked)
        self.model.appendRow(root2)

        # 二级节点:风景
        child2_1 = QStandardItem("风景")
        child2_1.setCheckable(True)
        child2_1.setCheckState(Qt.Unchecked)
        root2.appendRow(child2_1)

        # 三级节点:山川/湖泊
        child2_1_1 = QStandardItem("山川.jpg")
        child2_1_1.setCheckable(True)
        child2_1_1.setCheckState(Qt.Unchecked)
        child2_1.appendRow(child2_1_1)

        child2_1_2 = QStandardItem("湖泊.jpg")
        child2_1_2.setCheckable(True)
        child2_1_2.setCheckState(Qt.Unchecked)
        child2_1.appendRow(child2_1_2)

    def on_model_data_changed(self, top_left: QModelIndex, bottom_right: QModelIndex):
        """模型数据变化(勾选状态)联动处理"""
        if self._is_updating or top_left.column() != 0:  # 防递归/只处理第0列
            return

        self._is_updating = True
        try:
            item = self.model.itemFromIndex(top_left)
            current_state = item.checkState()
            # 1. 父节点变化:同步所有子节点
            self._set_child_check_state(item, current_state)
            # 2. 子节点变化:更新所有父节点
            self._update_parent_check_state(item.parent())
        finally:
            self._is_updating = False

    def _set_child_check_state(self, parent_item: QStandardItem, check_state):
        """递归设置子节点勾选状态"""
        row_count = parent_item.rowCount()
        for i in range(row_count):
            child = parent_item.child(i)
            if child.isCheckable():
                child.setCheckState(check_state)
                self._set_child_check_state(child, check_state)  # 递归处理孙子节点

    def _update_parent_check_state(self, parent_item: QStandardItem):
        """递归更新父节点状态(全选/半选/未选)"""
        if not parent_item:  # 无父节点则终止
            return

        row_count = parent_item.rowCount()
        checked_count = 0
        unchecked_count = 0

        # 统计子节点状态
        for i in range(row_count):
            child = parent_item.child(i)
            if child.isCheckable():
                state = child.checkState()
                if state == Qt.Checked:
                    checked_count += 1
                elif state == Qt.Unchecked:
                    unchecked_count += 1

        # 更新父节点状态
        if checked_count == row_count:
            parent_item.setCheckState(Qt.Checked)
        elif unchecked_count == row_count:
            parent_item.setCheckState(Qt.Unchecked)
        else:
            parent_item.setCheckState(Qt.PartiallyChecked)

        # 递归更新上层父节点
        self._update_parent_check_state(parent_item.parent())

    # 以下为原有右键菜单等功能(无修改)
    def show_right_menu(self, pos):
        index = self.tree_view.indexAt(pos)
        if not index.isValid():
            return

        menu = QMenu(self.tree_view)
        info_action = QAction("查看节点信息", self)
        info_action.triggered.connect(lambda: self.show_item_info(index))
        menu.addAction(info_action)

        toggle_action = QAction("勾选/取消勾选", self)
        toggle_action.triggered.connect(lambda: self.toggle_item_check(index))
        menu.addAction(toggle_action)

        menu.addSeparator()
        del_action = QAction("删除节点", self)
        del_action.triggered.connect(lambda: self.delete_item(index))
        menu.addAction(del_action)

        menu.exec_(self.tree_view.mapToGlobal(pos))

    def show_item_info(self, index):
        item = self.model.itemFromIndex(index)
        state_map = {
            Qt.Unchecked: "未勾选",
            Qt.PartiallyChecked: "半选",
            Qt.Checked: "已勾选"
        }
        check_state = state_map.get(item.checkState(), "未知")
        QMessageBox.information(
            self, "节点信息",
            f"名称:{item.text()}\n状态:{check_state}"
        )

    def toggle_item_check(self, index):
        item = self.model.itemFromIndex(index)
        current_state = item.checkState()
        new_state = Qt.Unchecked if current_state == Qt.Checked else Qt.Checked
        item.setCheckState(new_state)

    def delete_item(self, index):
        if not index.parent().isValid():
            self.model.removeRow(index.row())
        else:
            self.model.removeRow(index.row(), index.parent())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = TreeViewDemo()
    window.show()
    sys.exit(app.exec_())

相关文档

PyQt5列表介绍【树控件】-QTreeWidget

https://zhuanlan.zhihu.com/p/17204967078https://zhuanlan.zhihu.com/p/17204967078

PyQt5列表介绍【树控件】-QTreeView

https://zhuanlan.zhihu.com/p/17439921032https://zhuanlan.zhihu.com/p/17439921032

相关推荐
Java Fans16 小时前
PyQt多页面切换教程
pyqt
深蓝海拓1 天前
PySide6从0开始学习的笔记(五) 信号与槽
笔记·qt·学习·pyqt
深蓝海拓2 天前
PySide6从0开始学习的笔记(四)QMainWindow
笔记·python·学习·pyqt
深蓝海拓2 天前
PySide6 的 QSettings简单应用学习笔记
python·学习·pyqt
深蓝海拓3 天前
PySide6从0开始学习的笔记(三) 布局管理器与尺寸策略
笔记·python·qt·学习·pyqt
微尘hjx4 天前
【目标检测软件 01】YOLO识别软件功能与操作指南
人工智能·测试工具·yolo·目标检测·计算机视觉·ai·pyqt
深蓝海拓5 天前
自用pyside6项目模板
学习·pyqt
YANshangqian5 天前
跨平台桌面RSS阅读器 MrRSS
pyqt
Java Fans6 天前
Qt Designer 和 PyQt 开发教程
开发语言·qt·pyqt