📚 目录 第 11 章 表格展示:Treeview(通讯录核心展示控件)★★★

第 11 章 表格展示:Treeview(通讯录核心展示控件)★★★

本书红线(贯穿全教程,回顾):
1. 每个界面都用 class App: 封装,__init__ 里建控件,方法即事件回调。
2. 所有控件示例一律写在类里,不用脱离类的扁平脚本。
3. 代码块带 if __name__ == "__main__": 守卫,可独立运行。
4. 星级:★ 了解 · ★★ 理解 · ★★★ 掌握 · ★★★★ 重点必会。

主线项目:图形界面通讯录管理器,随章节每章新增一个控件慢慢长大。

上节回顾(第 10 章):你学会了用 Listbox 显示"一列"多条数据,并配合 Scrollbar 双向绑定实现滚动;还用 refresh(self) 方法从 self.contacts 数据重绘列表,建立了"改数据 → 调刷新 → 界面更新"的思维。本章给通讯录上最核心的展示控件——Treeview(表格),用多列表格显示姓名/电话/分类。

本章目标:认识 ttk.Treeview——一个能显示"多列结构化数据"的表格控件,是通讯录真正的核心展示区。重点是 columns 与 insert 的 values 顺序对应、show="headings" 显示列标题、heading/column 设标题与列宽、get_children()/delete(*...) 清空、selection()/item(..., "values") 取行数据,以及绑定 <<TreeviewSelect>> 点行事件。OOP 落点:self.tree 实例属性 + refresh(self) 从数据层重绘;点行回填表单(与第 12/14 章衔接)。


11.1 是什么 / 适用场景 ★

是什么:ttk.Treeview 是 tkinter 里最强大的"表格/树"控件。它能像 Excel 一样分多列显示数据(姓名 | 电话 | 分类),也支持树形层级(本章只用它的"表格"形态)。

适用场景:

📌 Treeview 是 ttk 控件,用 ttk.Treeview。它比 Listbox 强在"多列"——Listbox 只能一列文字,Treeview 能排成表格。

11.2 构造选项(对照官方手册)★★

来自官方 tkinter.ttk.Treeview 文档:

选项作用常用取值重要度
columns除首列外的数据列标识符(元组)("phone", "cat")★★★★
show显示哪些部分"headings"(只显示列标题)/"tree"(只首列)/"tree headings"★★★★
selectmode选择模式"browse"(单选行)/"extended"(多选行)★★
height可见行数10★★
🔴 show="headings" 时不显示首列 text:show="headings" 只画列标题,不画最左边的树形首列。所以所有数据(包括"姓名")都要放进 columns + values,首列 text 在 heading 模式下看不到。详见 11.4 例 11.2。

11.3 常用方法速查 ★★

方法作用示例
heading(列, text=)设某列标题文字tree.heading("phone", text="电话")
column(列, width=, anchor=)设列宽与对齐tree.column("phone", width=120, anchor="w")
insert(父, 位置, text=, values=)加一行;"" 父表示顶层,tk.END 末尾tree.insert("", tk.END, text="张三", values=("138…","同事"))
get_children()取所有行的 id 列表tree.get_children()
delete(*行id)删除指定行(解包删除全部)tree.delete(*tree.get_children())
selection()取选中行 id(元组)tree.selection()
item(行id, "values")取该行的 values 数据tree.item(sel[0], "values")
bind("<<TreeviewSelect>>", 回调)点行选中时触发tree.bind("<<TreeviewSelect>>", self.on_select)
insert 的 values 顺序必须和 columns 一一对应:第一个值是 columns[0],第二个是 columns[1],以此类推。次序错就会"电话显示成分类"。

11.4 详细例子(循序渐进)★★★

每个例子都写在 class App 里,可直接运行(本机会弹出窗口)。

例 11.1 Treeview 基础:插入 3 个联系人并设列标题 ★★★

先把表格建出来:定义列、设标题、加 3 行数据。(注意此例 show="headings" + 把"姓名"也放进 columns。)

# 第11章 例11.1:用 Treeview 建一个"姓名/电话/分类"表格
import tkinter as tk
from tkinter import ttk


class App:
    def __init__(self, root):
        self.root = root
        self.root.title("通讯录管理器 - 联系人表格")

        # ① 定义列:name/phone/cat 三个数据列(首列树形不用,全用 columns)
        self.tree = ttk.Treeview(
            self.root,
            columns=("name", "phone", "cat"),  # 列标识符
            show="headings",                    # 只显示列标题(不显示最左树形列)
            height=8,
        )
        # ② 给每列设标题文字
        self.tree.heading("name", text="姓名")
        self.tree.heading("phone", text="电话")
        self.tree.heading("cat", text="分类")
        # ③ 给每列设宽度与对齐(anchor="w" 左对齐,"center" 居中)
        self.tree.column("name", width=100, anchor="w")
        self.tree.column("phone", width=140, anchor="w")
        self.tree.column("cat", width=100, anchor="center")
        self.tree.pack(padx=20, pady=10, fill="both", expand=True)

        # ④ 插入 3 行;values 顺序必须和 columns 对应:(姓名, 电话, 分类)
        self.tree.insert("", tk.END, values=("张三", "13800000001", "同事"))
        self.tree.insert("", tk.END, values=("李四", "13800000002", "家人"))
        self.tree.insert("", tk.END, values=("王五", "13800000003", "客户"))


if __name__ == "__main__":
    root = tk.Tk()
    App(root)
    root.mainloop()

逐行说明:

例 11.2 清空重绘:refresh(self) + delete(*get_children()) ★★★

本章重点 OOP 落点:用 self.tree 实例属性 + refresh(self) 从数据层重绘整个表格。清空必须用 delete(*self.tree.get_children())。

# 第11章 例11.2:数据列表 + refresh() 驱动 Treeview 重绘
import tkinter as tk
from tkinter import ttk


class App:
    def __init__(self, root):
        self.root = root
        self.root.title("通讯录管理器 - 数据驱动表格")

        # ① 数据层:每条联系人是一个 (姓名, 电话, 分类) 元组
        self.data = [
            ("张三", "13800000001", "同事"),
            ("李四", "13800000002", "家人"),
            ("王五", "13800000003", "客户"),
        ]

        self.tree = ttk.Treeview(
            self.root, columns=("name", "phone", "cat"), show="headings", height=8,
        )
        self.tree.heading("name", text="姓名")
        self.tree.heading("phone", text="电话")
        self.tree.heading("cat", text="分类")
        self.tree.column("name", width=100, anchor="w")
        self.tree.column("phone", width=140, anchor="w")
        self.tree.column("cat", width=100, anchor="center")
        self.tree.pack(padx=20, pady=10, fill="both", expand=True)

        self.count_label = ttk.Label(self.root, text="共 0 位")
        self.count_label.pack(padx=20, pady=10)

        # ② 首次按数据重绘
        self.refresh()

    def refresh(self):
        # ③ 清空所有行:get_children() 取全部行 id,用 * 解包传给 delete
        self.tree.delete(*self.tree.get_children())
        # ④ 从数据层逐条插入
        for row in self.data:
            self.tree.insert("", tk.END, values=row)
        self.count_label.config(text=f"共 {len(self.data)} 位")


if __name__ == "__main__":
    root = tk.Tk()
    App(root)
    root.mainloop()

逐行说明:

例 11.3 点行回填:绑定 <<TreeviewSelect>> 显示选中行 ★★★

点表格的某一行,把那一行的信息显示在下方 Label。这是"点行 → 取数据 → 回填"的完整链路,直接衔接第 12/14 章的表单编辑。

# 第11章 例11.3:点击 Treeview 行,把该行数据回填到 Label
import tkinter as tk
from tkinter import ttk


class App:
    def __init__(self, root):
        self.root = root
        self.root.title("通讯录管理器 - 点行查看")

        self.data = [
            ("张三", "13800000001", "同事"),
            ("李四", "13800000002", "家人"),
            ("王五", "13800000003", "客户"),
        ]

        self.tree = ttk.Treeview(
            self.root, columns=("name", "phone", "cat"), show="headings", height=8,
        )
        self.tree.heading("name", text="姓名")
        self.tree.heading("phone", text="电话")
        self.tree.heading("cat", text="分类")
        self.tree.column("name", width=100, anchor="w")
        self.tree.column("phone", width=140, anchor="w")
        self.tree.column("cat", width=100, anchor="center")
        self.tree.pack(padx=20, pady=10, fill="both", expand=True)

        # ① 绑定"选中行"事件:点任意一行都会调用 on_select
        self.tree.bind("<<TreeviewSelect>>", self.on_select)

        self.label = ttk.Label(self.root, text="(点击上方任意一行查看详情)")
        self.label.pack(padx=20, pady=10)

        self.refresh()

    def refresh(self):
        self.tree.delete(*self.tree.get_children())
        for row in self.data:
            self.tree.insert("", tk.END, values=row)

    def on_select(self, event):
        # ② selection() 返回选中行 id 元组,可能为空要先判断
        sel = self.tree.selection()
        if not sel:
            return
        # ③ item(行id, "values") 取该行数据,返回 (姓名, 电话, 分类)
        name, phone, cat = self.tree.item(sel[0], "values")
        self.label.config(text=f"姓名:{name} 电话:{phone} 分类:{cat}")


if __name__ == "__main__":
    root = tk.Tk()
    App(root)
    root.mainloop()

逐行说明:


11.5 OOP 落点 ★★


11.6 注意事项 / 易错清单 ★★


11.7 本章练习

  1. 写一个程序:用 Treeview 建"姓名/电话"两列表格,手动 insert 3 行联系人数据,并给两列设好标题和宽度。
  2. 写一个程序:用 self.data 数据列表 + refresh(self) 方法重绘表格;点"清空"按钮调用 delete(*self.tree.get_children()) 清空全部行。
  3. 综合练习:手动插入 3 个联系人到 Treeview,绑定 <<TreeviewSelect>>,点某行把"姓名/电话/分类"显示在下方 Label(提示:item(sel[0], "values") 解包)。
  4. 思考题:如果把例 11.1 的 columns=("name","phone","cat") 但 insert 写成 values=("同事","张三","138..."),运行后表格会怎样?(提示:列标题是"姓名/电话/分类",但 values 顺序错乱,会显示成"同事/张三/138...",姓名和分类串列。务必让 values 与 columns 顺序一致。)
下节预告:第 12 章我们把 Treeview 和前面的表单控件(Entry/Combobox/Checkbutton 等)真正连起来——点表格里的联系人,自动把信息回填到上方表单供编辑;改完再保存回表格,通讯录从此"能看、能点、能改"。