📚 目录 第 13 章 分页与分隔:Notebook / PanedWindow(组织更多内容)

第 13 章 分页与分隔:Notebook / PanedWindow(组织更多内容)

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

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

上节回顾(第 12 章):你学会了用 Frame / LabelFrame 把界面拆成"区域",并把不同区域交给 _build_form、_build_list 等私有方法构建,做到"界面结构 = 代码结构"。但有时单屏内容太多——比如既想看"全部联系人"又想看"我的收藏",挤在一起还是乱。本章用 Notebook(分页) 和 PanedWindow(可拖拽分隔) 来组织更多内容。

本章目标:学会用 Notebook 做"标签页切换"、用 PanedWindow 做"可拖动分隔条"。OOP 落点是——每页(每窗格)内容各自一个构建方法,互不干扰。


13.1 是什么 / 适用场景 ★

Notebook 是什么:ttk.Notebook 是"标签页容器"。它像浏览器的标签页——上面一排页签("全部联系人""我的收藏"),点哪个就显示哪一页的内容,各页互相独立,但只占一份屏幕空间。

PanedWindow 是什么:ttk.PanedWindow 是"可拖拽分隔的窗格容器"。它把空间分成左右(或上下)几块,中间有一条能拖动的分隔条,用户可自己调整每块的宽窄。

适用场景:

不适用:只是简单分区用 Frame/LabelFrame(第 12 章)就够;要"弹窗"用 Toplevel(后续章节)。

📌 两者都是"容器中的容器"——你先在里面建子 Frame,再往子 Frame 里放真正的控件。

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

Notebook 常用选项(来自官方 tkinter.ttk.Notebook):

选项作用常用取值重要度
width / heightNotebook 整体宽 / 高(像素)整数★★
padding页签与内容之间的内边距整数★

PanedWindow 常用选项(来自官方 tkinter.ttk.PanedWindow):

选项作用常用取值重要度
orient分隔方向:"horizontal" 左右分、"vertical" 上下分"horizontal"/"vertical"★★★
showhandle是否显示拖拽手柄小方块True/False★★
sashrelief分隔条的立体样式"raised" 等★

13.3 常用方法速查 ★★

Notebook 方法:

方法作用示例
add(子控件, text="页签文字")添加一页(子控件通常是 Frame)nb.add(f, text="全部")★★★★
tab(页, 选项=值)读/改某页选项;tab(0, "text") 读第 0 页标题nb.tab(0, text="新名")★★★
select()返回当前选中页对应的控件对象;select(页) 可切换cur = nb.select()★★★
index("current")返回当前页的索引(0、1…)nb.index("current")★★
forget(页)删除某页nb.forget(0)★

PanedWindow 方法:

方法作用示例
add(子控件)添加一个子窗格pw.add(f)★★★★
sash_place(索引, x, y)用代码设置某条分隔条位置pw.sash_place(0, 120, 0)★
panecget(子控件, 选项)读某窗格的选项pw.panecget(f, "width")★

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

例 13.1 Notebook 做"全部联系人 / 我的收藏"两页 ★★★

最基础的 Notebook:建一个 Notebook,再建两个 Frame 当"页",分别 add 进去并设页签文字。

# 第13章 例13.1:用 ttk.Notebook 做"全部联系人 / 我的收藏"两页
import tkinter as tk
from tkinter import ttk


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

        # ① 创建 Notebook(分页容器),pack 占满窗口
        self.nb = ttk.Notebook(self.root)
        self.nb.pack(fill="both", expand=True)

        # ② 第一页:全部联系人(页本身是一个 Frame)
        self.page_all = ttk.Frame(self.nb)
        self.nb.add(self.page_all, text="全部联系人")   # 加页 + 设页签文字
        ttk.Label(self.page_all, text="这里是所有联系人").pack(padx=10, pady=10)

        # ③ 第二页:我的收藏
        self.page_fav = ttk.Frame(self.nb)
        self.nb.add(self.page_fav, text="我的收藏")
        ttk.Label(self.page_fav, text="这里是我收藏的联系人").pack(padx=10, pady=10)


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

逐行说明:

例 13.2 tab() / select() / index():显示当前在第几页 ★★★

演示如何用 tab() 读页签标题、index("current") 读当前页索引、select() 拿到当前页对象。

# 第13章 例13.2:按钮显示当前页(演示 select / index / tab)
import tkinter as tk
from tkinter import ttk


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

        self.nb = ttk.Notebook(self.root)
        self.nb.pack(fill="both", expand=True)

        # 两页(真实工程里会交给两个构建方法,见 13.5)
        self.page_all = ttk.Frame(self.nb)
        self.nb.add(self.page_all, text="全部联系人")
        self.page_fav = ttk.Frame(self.nb)
        self.nb.add(self.page_fav, text="我的收藏")

        # 一个按钮,点击后报告"当前在第几页"
        self.btn = ttk.Button(self.root, text="显示当前页", command=self.show_current)
        self.btn.pack(pady=5)

    def show_current(self):
        # nb.select() 返回当前页对应的控件对象(内部 id 字符串)
        current = self.nb.select()
        # nb.index("current") 返回当前页的索引(0、1…)
        idx = self.nb.index("current")
        # nb.tab(idx, "text") 读取该页签的标题文字
        title = self.nb.tab(idx, "text")
        print(f"当前页对象={current},是第 {idx} 页:{title}")


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

逐行说明:

例 13.3 PanedWindow:左右可拖拽分隔 ★★★

中间那条分隔条可以拖动,用户自己决定左右比例。

# 第13章 例13.3:ttk.PanedWindow 左右可拖分隔,左放表单右放列表
import tkinter as tk
from tkinter import ttk


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

        # orient="horizontal":左右两块,中间一条竖直分隔条
        self.pw = ttk.PanedWindow(self.root, orient="horizontal")
        self.pw.pack(fill="both", expand=True)

        # 左窗格:一个 Frame,add 进 PanedWindow
        self.left = ttk.Frame(self.pw, width=200)
        self.pw.add(self.left)
        ttk.Label(self.left, text="左:表单区").pack(padx=5, pady=5)
        ttk.Entry(self.left).pack(padx=5, pady=5, fill="x")

        # 右窗格
        self.right = ttk.Frame(self.pw)
        self.pw.add(self.right)
        ttk.Label(self.right, text="右:列表区").pack(padx=5, pady=5)
        for n in ("张三", "李四"):
            ttk.Label(self.right, text=n).pack(anchor="w", padx=8, pady=2)


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

逐行说明:


13.5 OOP 落点 ★★

Notebook / PanedWindow 天生适合"每页一个方法"的写法:

一句话:"一页一方法,容器持有页;切页不动其它,改页互不连累。"

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


13.7 本章练习

  1. 用 ttk.Notebook 做一个程序,含"设置""关于"两页,每页放一句说明文字。
  2. 在练习 1 基础上,加一个按钮"跳到关于页":点击时调用 self.nb.select(self.page_about) 切换过去。
  3. 用 ttk.PanedWindow(orient="vertical") 做上下分隔:上块放一个 Entry、下块放一个 Label 显示"下方内容"。
  4. 综合练习:用 Notebook 做"全部联系人 / 我的收藏"两页,每页各写一个 _build_page_all / _build_page_fav 私有构建方法(复现 13.5 思路)。
下节预告:第 14 章是本版核心章——我们把"一组常一起出现的控件"(如搜索栏、表单、表格)封装成自己的控件类(自定义组件),实现复用与解耦,这是 tkinter 工程化的关键一步,也为第 20 章 MVC 铺路。