You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()

功能说明

  1. 复选框联动:
    • 点击节点复选框,会自动同步所有子节点的选中状态;
    • 子节点状态变化时,父节点会自动更新为全选、半选或未选状态。
  2. 右键菜单:
    • 右键点击空白处:只能添加根节点;
    • 右键点击节点:可以给该节点添加子节点,或者给该节点的父级添加同级节点(父节点)。
  3. 报表生成:
    • 点击「生成选中节点报表」按钮,会弹出窗口展示所有选中节点的完整路径,格式为「父节点>子节点>子节点」。

依赖说明

代码用到了PIL库来生成复选框图标,安装命令:

pip install pillow

如果不想用PIL,也可以替换成tkinter内置的BitmapImage,比如用"gray""check""questhead"等内置bitmap来模拟三种状态。

内容的提问来源于stack exchange,提问作者user20102310

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 05:40:29