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/17204967078
https://zhuanlan.zhihu.com/p/17204967078
PyQt5列表介绍【树控件】-QTreeView
https://zhuanlan.zhihu.com/p/17439921032
https://zhuanlan.zhihu.com/p/17439921032