📚 目录 第 12 章 容器:Frame / LabelFrame(把复杂界面拆成"区域")

第 12 章 容器:Frame / LabelFrame(把复杂界面拆成"区域")

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

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

上节回顾(第 11 章):你学会了用 Treeview 在界面上展示"表格数据"——把联系人一行行列出来。但问题来了:当窗口里既有表格、又有输入框、又有按钮时,所有控件平铺在一起会越来越乱。本章引入两个"容器"控件——Frame 和 LabelFrame,帮你把界面拆成一个个清爽的"区域"。

本章目标:学会用 Frame(纯透明容器) 和 LabelFrame(带标题的框) 把界面分区。OOP 落点是——不同区域交给 App 类中不同的私有构建方法(如 _build_form、_build_list),做到"界面结构 = 代码结构"。


12.1 是什么 / 适用场景 ★

Frame 是什么:tk.Frame 是一个看不见的"盒子",它本身不显示任何文字或颜色(默认透明),唯一的作用就是装其它控件。你把 Label、Entry、Button 等"放"进一个 Frame,再把这个 Frame 整体摆进窗口——于是这些控件就成了一个"组合",可以一起移动、一起布局。

LabelFrame 是什么:ttk.LabelFrame 是 Frame 的"带标题加强版"——它四周有一圈边框,并且在左上角显示一段标题文字(如"联系人信息")。它特别适合用来圈住一组相关的表单控件。

适用场景:

不适用:需要"分页切换"用 Notebook(第 13 章);需要"可拖拽分隔"用 PanedWindow(第 13 章)。容器只负责"圈地",不负责"切换/拖动"。

📌 tk 与 ttk 的选择:Frame 两个版本都有(tk.Frame、ttk.Frame);LabelFrame 优先用 ttk.LabelFrame(主题更统一、更好看),tk.LabelFrame 也可用。本章示例统一用 ttk.LabelFrame + tk.Frame 演示。注意 ttk 控件没有 fg/bg,要改颜色得用 Style(后续章节讲),所以容器背景保持默认即可。

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

Frame / LabelFrame 继承 Widget,下面列出最常用选项(完整列表见官方 tkinter.Frame / tkinter.ttk.LabelFrame 文档):

选项作用常用取值重要度
textLabelFrame 专属:框左上角的标题文字text="联系人信息"★★★★
labelanchorLabelFrame 标题所在方位"n"/"nw"/"w"/"ne" 等★★
width / height容器宽 / 高(像素)整数,如 200★★
relief边框样式(LabelFrame 默认有边;Frame 默认 flat)"sunken"/"groove"/"raised"★★
borderwidth边框粗细(像素)整数★
bg / background仅 tk.Frame 支持背景色;ttk.Frame 用 Style颜色字符串★★
padx / pady容器自身的外边距(与子控件间距无关)整数★★
container设为 True 可承载其它 Tk 顶层窗口(高级,少用)True/False★
关键认知:Frame 默认"透明无边框"——你创建它后肉眼几乎看不出它存在,但它内部的控件布局会受影响(比如 pack/grid 的相对关系)。LabelFrame 则自带边框+标题,圈地效果一目了然。

12.3 常用方法速查 ★★

Frame / LabelFrame 是"容器",自身方法不多,主要是所有控件通用的那几个:

方法作用示例
pack() / grid() / place()把容器本身摆进父窗口,并把子控件摆进容器self.form.pack()
config() / configure()运行时改选项(LabelFrame 常用它改 text 标题)self.form.config(text="新标题")
winfo_children()返回容器内所有子控件组成的列表(调试/批量操作时有用)self.form.winfo_children()
destroy()销毁容器及其内部所有子控件self.form.destroy()
📌 winfo_children() 很实用:当你想"清空容器里所有东西重新来"时,可以遍历它逐一 destroy(),比一个个记变量方便。

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

例 12.1 用 tk.Frame 把界面分成"标题区"和"按钮区" ★★

最朴素的用法:用两个 Frame 把窗口上下切开,一个放标题、一个放按钮。

# 第12章 例12.1:用 tk.Frame 把界面分成"标题区"和"按钮区"两块
import tkinter as tk
from tkinter import ttk


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

        # ① 顶部标题区:创建一个 Frame 当容器,pack 到窗口上方、横向占满
        self.top = tk.Frame(self.root)
        self.top.pack(side="top", fill="x")
        # 在标题区里放 Label——注意父容器是 self.top(不是 root)!
        self.title_label = tk.Label(self.top, text="我的通讯录",
                                    font=("微软雅黑", 16))
        self.title_label.pack(padx=10, pady=10)

        # ② 底部按钮区:另一个 Frame,pack 到窗口下方
        self.bottom = tk.Frame(self.root)
        self.bottom.pack(side="bottom", fill="x")
        self.add_btn = ttk.Button(self.bottom, text="添加联系人")
        self.add_btn.pack(side="left", padx=5, pady=5)


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

逐行说明:

例 12.2 用 ttk.LabelFrame 做带标题的表单区 ★★★

表单控件很多时,用 LabelFrame 把它们圈起来并加上标题,用户一眼就懂这块是干嘛的。

# 第12章 例12.2:用 ttk.LabelFrame 做带标题的"基本信息"表单区
import tkinter as tk
from tkinter import ttk


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

        # LabelFrame:带标题的框,text 就是左上角的标题文字
        self.form = ttk.LabelFrame(self.root, text="基本信息")
        self.form.pack(padx=10, pady=10, fill="x")

        # 表单内第 0 行:姓名标签 + 输入框(父容器都是 self.form)
        self.name_label = ttk.Label(self.form, text="姓名:")
        self.name_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
        self.name_entry = ttk.Entry(self.form)
        self.name_entry.grid(row=0, column=1, padx=5, pady=5)

        # 表单内第 1 行:电话
        self.phone_label = ttk.Label(self.form, text="电话:")
        self.phone_label.grid(row=1, column=0, padx=5, pady=5, sticky="e")
        self.phone_entry = ttk.Entry(self.form)
        self.phone_entry.grid(row=1, column=1, padx=5, pady=5)


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

逐行说明:

例 12.3 左表单 + 右列表:分区交给不同"私有构建方法" ★★★★

这是本章 OOP 落点的核心示范——把"左区"和"右区"分别交给 _build_form 和 _build_list 两个私有方法构建,主 __init__ 只负责总布局。界面结构 = 代码结构。

# 第12章 例12.3:左 LabelFrame 放表单、右 Frame 放列表,分区由私有方法构建
import tkinter as tk
from tkinter import ttk


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

        # 外层总布局:一个 body Frame 横向分左右两列
        self.body = tk.Frame(self.root)
        self.body.pack(fill="both", expand=True)
        self.body.columnconfigure(0, weight=1)   # 第0列(左)可伸缩
        self.body.columnconfigure(1, weight=1)   # 第1列(右)可伸缩

        # 把两块区域分别交给两个私有方法构建(OOP 落点)
        self._build_form()   # 构建左区(表单)
        self._build_list()   # 构建右区(列表)

    def _build_form(self):
        # 左区:带标题的表单框,放在 body 的第 0 列
        self.form = ttk.LabelFrame(self.body, text="联系人信息")
        self.form.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
        ttk.Label(self.form, text="姓名:").grid(row=0, column=0, padx=4, pady=4, sticky="e")
        self.name_entry = ttk.Entry(self.form)
        self.name_entry.grid(row=0, column=1, padx=4, pady=4)
        ttk.Label(self.form, text="电话:").grid(row=1, column=0, padx=4, pady=4, sticky="e")
        self.phone_entry = ttk.Entry(self.form)
        self.phone_entry.grid(row=1, column=1, padx=4, pady=4)

    def _build_list(self):
        # 右区:一个普通 Frame 当列表容器,放在 body 的第 1 列
        self.list_box = tk.Frame(self.body)
        self.list_box.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
        ttk.Label(self.list_box, text="联系人列表").pack(anchor="w")
        for name in ("张三", "李四", "王五"):
            ttk.Label(self.list_box, text=name).pack(anchor="w", padx=6, pady=2)


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

逐行说明:

运行例 12.3:左边是带"联系人信息"标题的表单框,右边是"联系人列表"标题下竖排的若干名字——界面清爽分区,代码结构也清爽。

12.5 OOP 落点 ★★

本章把"容器"和"面向对象"紧密结合,核心有三点:

一句话:"用 Frame 圈地,用私有方法分区;界面分区清晰,代码自然清晰。"

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


12.7 本章练习

  1. 写一个程序:用 tk.Frame 把窗口分成上下两块,上块一个 Label 显示"通讯录",下块一个 Button 显示"退出"(提示:用 command=root.destroy)。
  2. 用 ttk.LabelFrame(text="登录信息") 圈起"用户名/密码"两个 Entry,做出一个带标题的登录框(密码 Entry 用 show="*")。
  3. 本章综合练习:左边 ttk.LabelFrame 放"姓名/电话"两个输入,右边 tk.Frame 用 pack 竖排几个联系人名字;用 _build_form / _build_list 两个私有方法分别构建(即复现例 12.3)。
  4. 思考题:例 12.3 如果把 _build_form 和 _build_list 里所有控件直接写进 __init__,功能会变吗?可读性/可维护性有什么差别?(提示:功能一样,但方法会越写越长、难找;分方法才是工程化做法。)
下节预告:第 13 章我们用 Notebook(分页) 和 PanedWindow(可拖拽分隔) 组织"更多内容"——当单屏放不下所有功能时,用标签页切换、用拖动条分屏,界面更从容。