1.TreeWidget实现树形列表 勾选与右键菜单
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实现树形列表 勾选与右键菜单
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