Python实现带复选框的Treeview:右键增节点、联动及报表方案咨询
Python实现带复选框的层级Treeview(含联动与报表功能)
工具选择:优先用ttk.Treeview
直接选tkinter的ttk.Treeview就够了——tix是老旧的UI库,现在基本被淘汰,而ttk是tkinter的标准扩展组件,兼容性强、社区资源充足,完全能满足你的需求。
核心功能实现
下面是完整的可运行代码,包含所有你要的功能:带复选框的层级树、右键菜单添加节点、复选框联动逻辑、选中节点报表生成。
完整代码
import tkinter as tk from tkinter import ttk, messagebox from PIL import Image, ImageDraw, ImageTk class CheckboxTreeview(ttk.Treeview): def __init__(self, master=None, **kw): super().__init__(master, **kw) # 初始化复选框状态图(全选、半选、未选) self._setup_checkbox_images() # 配置列 self.column("#0", width=220) self.heading("#0", text="节点结构") # 绑定事件 self.bind("<Button-1>", self._handle_click) self.bind("<Button-3>", self._show_right_click_menu) # 右键菜单 self.right_menu = tk.Menu(self, tearoff=0) self.right_menu.add_command(label="添加父节点", command=self._add_parent_node) self.right_menu.add_command(label="添加子节点", command=self._add_child_node) # 存储每个节点的选中状态:0=未选,1=半选,2=全选 self.node_status = {} def _setup_checkbox_images(self): # 生成16x16的复选框图标 self.img_unchecked = self._create_icon("unchecked") self.img_checked = self._create_icon("checked") self.img_indeterminate = self._create_icon("indeterminate") def _create_icon(self, status): img = Image.new("RGB", (16, 16), "white") draw = ImageDraw.Draw(img) # 绘制复选框边框 draw.rectangle((1, 1, 14, 14), outline="#333", width=1) if status == "checked": # 全选状态:绘制对勾 draw.line((3, 8, 7, 12, 13, 4), fill="#009900", width=2) elif status == "indeterminate": # 半选状态:填充橙色矩形 draw.rectangle((3, 3, 13, 13), fill="#FF9933", outline="#FF9933") return ImageTk.PhotoImage(img) def _handle_click(self, event): # 判断是否点击了复选框区域 row_item = self.identify_row(event.y) if not row_item: return # 获取#0列的区域,判断点击位置是否在复选框范围内 bbox = self.bbox(row_item, "#0") if bbox and event.x < bbox[0] + 20: self._toggle_node_status(row_item) def _toggle_node_status(self, item): # 切换当前节点的选中状态 current_status = self.node_status.get(item, 0) new_status = 2 if current_status != 2 else 0 self._set_node_status(item, new_status) # 同步更新所有子节点状态 self._sync_children_status(item, new_status) # 向上更新父节点状态 self._update_parent_status(item) def _set_node_status(self, item, status): # 设置节点状态并更新显示图标 self.node_status[item] = status if status == 0: self.item(item, image=self.img_unchecked) elif status == 1: self.item(item, image=self.img_indeterminate) elif status == 2: self.item(item, image=self.img_checked) def _sync_children_status(self, parent_item, status): # 递归同步所有子节点的状态 children = self.get_children(parent_item) for child in children: self._set_node_status(child, status) self._sync_children_status(child, status) def _update_parent_status(self, item): # 递归更新父节点的状态 parent_item = self.parent(item) if not parent_item: return # 获取所有子节点的状态 child_statuses = [self.node_status.get(child, 0) for child in self.get_children(parent_item)] # 判断父节点应该是什么状态 if all(s == 2 for s in child_statuses): new_status = 2 elif any(s in (1, 2) for s in child_statuses): new_status = 1 else: new_status = 0 # 状态变化时才更新,避免无限递归 if self.node_status.get(parent_item, 0) != new_status: self._set_node_status(parent_item, new_status) self._update_parent_status(parent_item) def _show_right_click_menu(self, event): # 记录右键点击的节点,弹出菜单 self.clicked_item = self.identify_row(event.y) self.right_menu.post(event.x_root, event.y_root) def _add_parent_node(self): # 添加父节点:点击空白处则添加为根节点,否则添加到当前节点的父级下 parent = self.parent(self.clicked_item) if self.clicked_item else "" new_node = self.insert(parent, "end", text=f"新节点{len(self.get_children(parent)) + 1}") self._set_node_status(new_node, 0) def _add_child_node(self): # 添加子节点:必须选中节点才能添加 if not self.clicked_item: messagebox.showwarning("提示", "请先选择一个节点作为父节点") return new_node = self.insert(self.clicked_item, "end", text=f"子节点{len(self.get_children(self.clicked_item)) + 1}") self._set_node_status(new_node, 0) def generate_selected_report(self): # 生成选中节点的路径报表,格式为"父>子>孙" report_lines = [] def traverse_node(item): status = self.node_status.get(item, 0) if status in (1, 2): # 回溯获取从根到当前节点的完整路径 path = [] current = item while current: path.insert(0, self.item(current)["text"]) current = self.parent(current) report_lines.append(">".join(path)) # 递归遍历子节点 for child in self.get_children(item): traverse_node(child) # 遍历所有根节点 for root_node in self.get_children(""): traverse_node(root_node) return "\n".join(report_lines) if __name__ == "__main__": main_win = tk.Tk() main_win.title("带复选框的层级Treeview") main_win.geometry("650x450") # 创建树形组件 tree = CheckboxTreeview(main_win) tree.pack(fill="both", expand=True, padx=15, pady=15) # 添加测试节点 root_node1 = tree.insert("", "end", text="项目A") tree._set_node_status(root_node1, 0) child_node1 = tree.insert(root_node1, "end", text="模块A1") tree._set_node_status(child_node1, 0) # 生成报表按钮 def display_report(): report_content = tree.generate_selected_report() if not report_content: messagebox.showinfo("报表", "没有选中任何节点") return # 弹出窗口显示报表 report_win = tk.Toplevel(main_win) report_win.title("选中节点结构报表") report_win.geometry("500x300") text_widget = tk.Text(report_win, wrap="word", font=("Arial", 10)) text_widget.pack(fill="both", expand=True, padx=10, pady=10) text_widget.insert("1.0", report_content) text_widget.config(state="disabled") report_btn = ttk.Button(main_win, text="生成选中节点报表", command=display_report) report_btn.pack(pady=5) main_win.mainloop()
功能说明
- 复选框联动:
- 点击节点复选框,会自动同步所有子节点的选中状态;
- 子节点状态变化时,父节点会自动更新为全选、半选或未选状态。
- 右键菜单:
- 右键点击空白处:只能添加根节点;
- 右键点击节点:可以给该节点添加子节点,或者给该节点的父级添加同级节点(父节点)。
- 报表生成:
- 点击「生成选中节点报表」按钮,会弹出窗口展示所有选中节点的完整路径,格式为「父节点>子节点>子节点」。
依赖说明
代码用到了PIL库来生成复选框图标,安装命令:
pip install pillow
如果不想用PIL,也可以替换成tkinter内置的BitmapImage,比如用"gray""check""questhead"等内置bitmap来模拟三种状态。
内容的提问来源于stack exchange,提问作者user20102310
相关产品推荐
相关产品推荐

