📚 目录 第 7 章 统一外观:ttk 模块与 Style(让界面换皮肤)

第 7 章 统一外观:ttk 模块与 Style(让界面换皮肤)

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

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

上节回顾(第 6 章):你认识了 Text 多行输入控件,学会了用 get("1.0", tk.END) 取全部、insert(tk.END, ...) 追加、delete("1.0", tk.END) 清空,还用 tag 给一段文字上色。现在你的通讯录管理器已经能"显示、点击、单行输入、多行输入"了,但界面还是 tkinter 默认的"土味原生"风格。

本章目标:认识 ttk(Themed Tkinter,主题控件)模块 和 Style(样式对象),把界面从土味原生换成随系统更美观的现代风格,并学会统一换肤。本章正式引出 ttk,之后本书优先使用 ttk 控件。


7.1 是什么 / 适用场景 ★

是什么:ttk 是 tkinter 的"主题控件"子模块。它把控件分成"功能(逻辑)"和"外观(主题)"两层——控件长什么样由当前"主题"决定,而不是靠你一个个设 fg/bg。它提供的控件叫"主题控件",如 ttk.Button、ttk.Label、ttk.Entry 等。

为什么要有 ttk:原生 tk.Button 的样式(灰色凸起按钮)又老又土,且在不同系统上观感不一致。ttk 控件随操作系统主题自动变好看,也方便统一换肤。

适用场景:

📌 本章起本书的约定:优先用 ttk.XXX(如 ttk.Button、ttk.Label、ttk.Entry)。但前几章已学的 tk.Label 等仍然有效,本章先对比二者差别。

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

ttk 控件选项和 tk 控件不同——它大多没有 fg/bg/font 这些直接样式选项,外观交给 Style 管。下面以 ttk.Button 为例(来自官方 tkinter.ttk 文档):

选项作用常用取值重要度
text按钮上文字"添加"★★★★
command点击回调(同 tk)self.on_click★★★★
style套用哪个自定义样式名"My.TButton"★★★
state可用/禁用"normal"/"disabled"★★★
width宽度(字符数)整数★★
image / compound图片及图文位置同 tk★
padding内边距(替代 tk 的 padx/pady 思路)整数或元组★★
⚠️ ttk 控件没有 fg/bg!想改颜色必须用 ttk.Style().configure(...)。若你写 ttk.Button(..., fg="red") 会被忽略(不报错但无效)。这是新手最常踩的坑,见 7.6 节。

7.3 常用方法速查 ★★

本章重点是 ttk.Style 对象的方法:

方法作用示例
ttk.Style()创建一个样式管理器对象self.style = ttk.Style()
style.theme_names()列出可用主题名print(self.style.theme_names())
style.theme_use()查看当前主题self.style.theme_use()
style.theme_use("clam")切换到指定主题self.style.theme_use("clam")
style.configure("TButton", ...)设置某类控件的样式style.configure("TButton", foreground="red")
style.map("TButton", foreground=[...])设置"状态变化"时的样式(如悬停变色)style.map("TButton", foreground=[("active","red")])
🔑 样式名规则:ttk 用 "T类型" 作为类名,如 TButton、TLabel、TEntry、TFrame。configure("TButton", ...) 就是"改所有按钮的样式"。自定义样式名则写成 "我的名字.TButton",再用控件 style="我的名字.TButton" 套用。

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

本章先对比 tk.Label 与 ttk.Label,之后示例优先用 ttk。注意:用 ttk 控件前要先 from tkinter import ttk。

例 7.1 tk.Label 与 ttk.Label 对比(先看差别)★

# 第7章 例7.1:并排显示原生 tk.Label 和主题 ttk.Label,直观对比外观
import tkinter as tk
from tkinter import ttk                     # 引入 ttk 子模块


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

        # 原生 tk.Label:样式靠 fg/bg 直接设定
        self.tk_label = tk.Label(
            self.root, text="我是原生 tk.Label", bg="#3a7afe", fg="white"
        )
        self.tk_label.pack(padx=10, pady=10)

        # ttk.Label:外观由主题决定,不能直接传 fg/bg
        self.ttk_label = ttk.Label(self.root, text="我是主题 ttk.Label")
        self.ttk_label.pack(padx=10, pady=10)


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

逐行说明:

例 7.2 切换主题(theme_use)看差别 ★★

ttk.Style().theme_use() 能查看/切换主题。常见主题有 'clam'、'alt'、'default'、'classic'(具体可用 theme_names() 查)。

# 第7章 例7.2:用 Style 查看并切换主题,做一个"换皮肤"按钮
import tkinter as tk
from tkinter import ttk


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

        # 创建样式管理器
        self.style = ttk.Style()
        print("当前可用主题:", self.style.theme_names())
        print("当前主题:", self.style.theme_use())

        self.label = ttk.Label(self.root, text="通讯录")
        self.label.pack(padx=10, pady=10)

        self.btn = ttk.Button(self.root, text="切换到 clam 主题", command=self.on_switch)
        self.btn.pack(padx=10, pady=10)

    def on_switch(self):
        # 切换到 'clam' 主题,整个界面风格立刻变化
        self.style.theme_use("clam")
        print("已切换到主题:", self.style.theme_use())


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

逐行说明:

例 7.3 用 Style 自定义外观 + 悬停变色(configure / map)★★★

style.configure 设置常态样式;style.map 设置"状态变化"时的样式(如鼠标悬停变红)。

# 第7章 例7.3:用 Style 自定义按钮颜色,并用 map 实现悬停变色
import tkinter as tk
from tkinter import ttk


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

        self.style = ttk.Style()
        # ① configure:设置所有 TButton 的常态样式(背景、前景、字体)
        self.style.configure(
            "TButton",
            background="#3a7afe",
            foreground="white",
            font=("微软雅黑", 12),
        )
        # ② map:当按钮处于 "active"(鼠标悬停/按下)状态时,前景变红
        self.style.map(
            "TButton",
            foreground=[("active", "red")],
            background=[("active", "#2a5fd0")],
        )

        self.btn = ttk.Button(self.root, text="悬停看看我", command=lambda: print("clicked"))
        self.btn.pack(padx=20, pady=20)


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

逐行说明:

例 7.4 把前几章的界面改写成 ttk 版 + 集中管理样式 ★★★

本章 OOP 落点:把样式集中放在一个地方(这里放 App.__init__ 开头的 _setup_style 方法),避免样式散落。下面把"标题 + 添加按钮 + 输入框"用 ttk 重写。

# 第7章 例7.4:用 ttk 重写"标题+按钮+输入框",并把样式集中到 _setup_style
import tkinter as tk
from tkinter import ttk


class App:
    def __init__(self, root):
        self.root = root
        self.root.title("通讯录管理器")
        self._setup_style()        # ① 先集中设好样式
        self._build_widgets()      # ② 再建界面

    def _setup_style(self):
        # 把所有样式集中在这里,界面代码就干净了
        self.style = ttk.Style()
        self.style.theme_use("clam")
        self.style.configure("Title.TLabel", font=("微软雅黑", 16),
                             foreground="#3a7afe", padding=8)
        self.style.configure("Action.TButton", foreground="white",
                             background="#3a7afe", padding=6)

    def _build_widgets(self):
        # 套用自定义样式名 "Title.TLabel"
        self.title = ttk.Label(self.root, text="我的通讯录", style="Title.TLabel")
        self.title.pack(pady=5)

        self.name_var = tk.StringVar()
        self.entry = ttk.Entry(self.root, textvariable=self.name_var, width=20)
        self.entry.pack(pady=5)

        # 套用自定义样式名 "Action.TButton"
        self.add_btn = ttk.Button(
            self.root, text="添加", style="Action.TButton", command=self.on_add
        )
        self.add_btn.pack(pady=5)

    def on_add(self):
        print("添加:", self.name_var.get())


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

逐行说明:


7.5 OOP 落点 ★★

一句话:ttk 控件负责"干活",Style 负责"好看";把 Style 收进类的一个方法,界面从此整洁又统一。

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


7.7 本章练习

  1. 写一个程序:用 ttk.Style() 查看当前系统有哪些可用主题(打印 theme_names())。
  2. 把第 3 章的"点击改变 Label 文字"程序改写成 ttk 版(ttk.Label + ttk.Button),并用 Style.configure 给按钮设蓝底白字。
  3. 在练习 2 基础上,用 style.map 让按钮在鼠标悬停时前景变红色。
  4. 综合题:把第 4~6 章里你写的 Label / Button / Entry / Text 示例,全部改用 ttk 对应控件(Text 也有 ttk 吗?提示:ttk 没有 Text,Text 仍用 tk.Text,其余 Label/Button/Entry 可 ttk 化),并用一个 Style 统一主题,观察界面风格变化。
下节预告:第 8 章我们将认识 Frame 容器控件——把相关控件"装进一个框"做分组,再结合 ttk 做出更具结构感的通讯录界面。