本书红线(贯穿全教程,回顾):
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 章衔接)。
是什么:ttk.Treeview 是 tkinter 里最强大的"表格/树"控件。它能像 Excel 一样分多列显示数据(姓名 | 电话 | 分类),也支持树形层级(本章只用它的"表格"形态)。
适用场景:
📌 Treeview 是ttk控件,用ttk.Treeview。它比 Listbox 强在"多列"——Listbox 只能一列文字,Treeview 能排成表格。
来自官方 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。
| 方法 | 作用 | 示例 |
|---|---|---|
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],以此类推。次序错就会"电话显示成分类"。
每个例子都写在 class App 里,可直接运行(本机会弹出窗口)。
先把表格建出来:定义列、设标题、加 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()逐行说明:
columns=("name","phone","cat") 声明三列;show="headings" 表示只画列标题栏。heading("name", text="姓名"):把 name 这列的标题写成"姓名"。每列都要 heading 一次,否则标题是空的列标识符。values 顺序 = columns 顺序:("张三", "138...", "同事") 依次对应 name/phone/cat。把电话和分类写反就会串列。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()逐行说明:
self.tree.delete(self.tree.get_children()):get_children() 返回行 id 列表, 把它解包成多个参数传给 delete,一次删除全部。写成 delete(self.tree.get_children()) 会报错(需要一个一个传)。values=row:row 是 (姓名,电话,分类) 元组,顺序天然对应 columns,不会串列。<<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()逐行说明:
self.tree.bind("<<TreeviewSelect>>", self.on_select):这是 Treeview 专用的"选中行"虚拟事件。用户点行即触发 on_select。self.tree.selection() 返回选中行 id(元组)。先 if not sel: 判断,避免空选报错。self.tree.item(sel[0], "values") 取该行存的数据,解包成 name, phone, cat,直接回填 Label。这就是"点行取数"的标准写法,第 12/14 章编辑表单时一模一样。self.tree 实例属性:表格控件收进 self,所有方法都能访问。refresh(self) 从数据层重绘:和第 10 章 Listbox 的 refresh 一脉相承——self.data 是真相来源,refresh() 清空 + 重插,保证界面永远等于数据。Treeview 因为多列,values 顺序对应 columns 是关键约束。on_select 里用 item(selection, "values") 取数,第 12/14 章会把这些数据填进 Entry/Combobox 让用户修改,再 refresh() 保存回表格。Treeview 把前面所有控件(Label/Button/Checkbutton/Combobox/Listbox)串成一个真正的通讯录界面。show="headings" 时不显示首列 text:在 heading 模式下,最左边的树形首列被隐藏,insert(..., text="张三", ...) 的 text 看不到。要让"姓名"可见,必须把姓名也加进 columns 并用 values 传(如例 11.1/11.2)。若想要首列显示树形文字,需 show="tree headings" 且不把首列放 columns。insert 的 values 顺序要对应 columns:columns=("phone","cat") 却写 values=("同事","138..."),电话和分类就反了。一致顺序最重要。delete(self.tree.get_children()):get_children() 返回列表,delete 需要逐个参数,必须加 解包:delete(self.tree.get_children())。selection() / curselection() 都可能为空:取数前一定先 if sel: 判断,否则下标越界。heading 忘记设:列标题会显示成列标识符(如 phone)而不是"电话"。width 单位是像素:不是字符数,设小了文字被截断。insert 3 行联系人数据,并给两列设好标题和宽度。self.data 数据列表 + refresh(self) 方法重绘表格;点"清空"按钮调用 delete(*self.tree.get_children()) 清空全部行。<<TreeviewSelect>>,点某行把"姓名/电话/分类"显示在下方 Label(提示:item(sel[0], "values") 解包)。columns=("name","phone","cat") 但 insert 写成 values=("同事","张三","138..."),运行后表格会怎样?(提示:列标题是"姓名/电话/分类",但 values 顺序错乱,会显示成"同事/张三/138...",姓名和分类串列。务必让 values 与 columns 顺序一致。)下节预告:第 12 章我们把 Treeview 和前面的表单控件(Entry/Combobox/Checkbutton 等)真正连起来——点表格里的联系人,自动把信息回填到上方表单供编辑;改完再保存回表格,通讯录从此"能看、能点、能改"。