本书红线(贯穿全教程,回顾):
1. 每个界面都用class App:封装,__init__里建控件,方法即事件回调。
2. 所有控件示例一律写在类里,不用脱离类的扁平脚本。
3. 代码块带if __name__ == "__main__":守卫,可独立运行。
4. 星级:★ 了解 · ★★ 理解 · ★★★ 掌握 · ★★★★ 重点必会。
主线项目:图形界面通讯录管理器,随章节每章新增一个控件慢慢长大。
上节回顾(第 8 章):你学会了用 Checkbutton 做"勾选/多选"(绑 BooleanVar,True/False),用 Radiobutton 做"单选"(一组共用一个 variable,各钮 value 不同)。本章给通讯录加上更省空间的"选择/调数"控件——Combobox(下拉框)、Scale(滑块)、Spinbox(数字微调框)。
本章目标:认识三个"省空间"控件——Combobox(下拉选一个值)、Scale(拖动滑块选连续数值)、Spinbox(点上下箭头调数字)。重点是 from_ 带下划线的坑、Combobox 用 state="readonly" 防止手输、get()/set() 取值设值。OOP 落点:把它们都收进 self,用方法读取用户的选择回填到 Label 或数据。
| 控件 | 是什么 | 通讯录用法 |
|---|---|---|
ttk.Combobox | 下拉框:平时只显示当前选中的一项,点开才列出全部选项 | 选"分类:同事/家人/客户" |
ttk.Scale | 滑块:拖动小圆点在一个范围内连续取值 | 选"年龄:0–120" |
ttk.Spinbox | 数字微调框:一个框 + 上下箭头,点箭头在范围内 +1/-1 | 选"第几组 / 年龄精确数" |
对比第 8 章:选项少(2–3 个)且想一眼看到全部 → 用 Radiobutton;选项多(5 个以上)或想省地方 → 用 Combobox;值是连续数字(如年龄、音量)→ Scale;值是整数步进 → Spinbox。
下面选项来自官方 tkinter.ttk 文档(Combobox / Scale / Spinbox):
| 控件 | 选项 | 作用 | 常用取值 | 重要度 |
|---|---|---|---|---|
| Combobox | values | 下拉里的可选项(元组/列表) | ("同事","家人","客户") | ★★★★ |
| Combobox | state | 是否可手输 | "readonly"(禁手输,只能选) / "normal" | ★★★ |
| Combobox | textvariable | 绑定变量读取值 | tk.StringVar() | ★★★ |
| Scale | from_ | 最小值(注意下划线!) | 0 | ★★★★ |
| Scale | to | 最大值 | 120 | ★★★★ |
| Scale | orient | 方向 | "horizontal" / "vertical" | ★★ |
| Scale | variable | 绑定变量 | tk.DoubleVar() / tk.IntVar() | ★★★ |
| Spinbox | from_ | 最小值(注意下划线) | 0 | ★★★★ |
| Spinbox | to | 最大值 | 100 | ★★★★ |
| Spinbox | increment | 每点一次的步长 | 1 | ★★★ |
| Spinbox | textvariable | 绑定变量 | tk.IntVar() | ★★★ |
| 三者通用 | command | 值变化时回调 | self.on_change | ★★★ |
📌 全章最致命的坑:Scale/Spinbox的最小值是from_(带下划线),不是from。from是 Python 关键字,直接写会语法报错。给 Combobox 设state="readonly"能避免用户乱输脏数据。
| 控件 | 方法 | 作用 | 示例 |
|---|---|---|---|
| Combobox | get() | 取当前选中的文字 | cb.get() |
| Combobox | set(值) | 设置显示的文字(必须存在于 values) | cb.set("家人") |
| Combobox | current(索引) | 按位置选中第几项(0 起) | cb.current(0) |
| Scale | get() | 取当前数值 | scale.get() |
| Scale | set(值) | 设置滑块位置 | scale.set(30) |
| Spinbox | get() | 取当前数字(字符串) | sb.get() |
| Spinbox | get() + int() | 取整数 | int(sb.get()) |
三个控件都能用textvariable/variable绑定变量,然后var.get()读、var.set()写——和第 8 章的变量思路一致。也可直接用控件自带的get()/set(),效果等价。
每个例子都写在 class App 里,可直接运行(本机会弹出窗口)。
通讯录里给联系人选"同事/家人/客户",用下拉框最省空间。
# 第9章 例9.1:用 Combobox 选择联系人"分类"
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器 - 选择分类")
# ① 创建 Combobox:values 是下拉里的选项;state="readonly" 禁止用户手输
self.cat_cb = ttk.Combobox(
self.root,
values=("同事", "家人", "客户"), # 下拉可选项
state="readonly", # 只能从列表选,不能乱打字
)
self.cat_cb.current(0) # ② 默认选中第 0 项("同事")
self.cat_cb.pack(padx=20, pady=10)
# ③ 确认按钮:点击后把选中项显示在 Label
self.btn = ttk.Button(self.root, text="确认分类", command=self.confirm)
self.btn.pack(padx=20, pady=10)
self.label = ttk.Label(self.root, text="(请选择分类)")
self.label.pack(padx=20, pady=10)
def confirm(self):
# ④ get() 直接拿到下拉当前选中的文字
self.label.config(text=f"分类:{self.cat_cb.get()}")
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
values=("同事","家人","客户") 给下拉提供可选项;state="readonly" 是关键——用户没法手打一个不存在的值,数据更干净。self.cat_cb.current(0) 让初始就选中第一项,避免空着。self.cat_cb.get() 读当前选中的文字,返回 "同事" 等字符串。年龄是连续数值,用滑块最直观。
# 第9章 例9.2:用 Scale 滑块选择"年龄"
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器 - 年龄")
# ① 滑块:from_ 是最小值(注意下划线!),to 是最大值,horizontal 水平摆放
self.age_scale = ttk.Scale(
self.root,
from_=0, # 最小值 0(务必写成 from_,不是 from)
to=120, # 最大值 120
orient="horizontal", # 水平滑块
command=self.on_scale, # 拖动时持续触发
)
self.age_scale.set(20) # ② 初始值设为 20
self.age_scale.pack(padx=20, pady=10, fill="x")
self.label = ttk.Label(self.root, text="年龄:20")
self.label.pack(padx=20, pady=10)
def on_scale(self, value):
# ③ value 是字符串,转成 int 显示;拖动时实时更新
age = int(float(value))
self.label.config(text=f"年龄:{age}")
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
from_=0 必须带下划线。from 是 Python 关键字,写成 from=0 会直接语法报错。这是全章最高频的坑。self.age_scale.set(20) 把滑块初始停在 20。command 回调会传入当前值(字符串),用 float() 再 int() 转成整数显示。每次拖动都会触发,实现"实时显示"。Spinbox 点上下箭头在范围内调整数;和 Combobox 一起做更完整的表单。
# 第9章 例9.3:用 Spinbox 调"亲密指数",与 Combobox 组合显示
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器 - 添加")
# ① Combobox 选分类(只读)
self.cat_cb = ttk.Combobox(
self.root, values=("同事", "家人", "客户"), state="readonly",
)
self.cat_cb.current(0)
self.cat_cb.pack(padx=20, pady=5)
# ② Spinbox:from_=1, to=10, increment=1,点箭头在 1~10 间步进
self.score_sb = ttk.Spinbox(
self.root,
from_=1, # 最小值 1(下划线!)
to=10, # 最大值 10
increment=1, # 每点一次 ±1
wrap=True, # 到边界后绕回另一头(可选)
)
self.score_sb.set(5) # ③ 初值 5
self.score_sb.pack(padx=20, pady=5)
self.btn = ttk.Button(self.root, text="保存", command=self.save)
self.btn.pack(padx=20, pady=10)
self.label = ttk.Label(self.root, text="(等待保存)")
self.label.pack(padx=20, pady=10)
def save(self):
cat = self.cat_cb.get() # ④ 读下拉文字
score = int(self.score_sb.get()) # ⑤ 读微调框数字(转 int)
self.label.config(text=f"分类:{cat} 亲密指数:{score}")
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
ttk.Spinbox(from_=1, to=10, increment=1):点上下箭头让数字在 1–10 之间每次变 1。set(5) 设初值。int(self.score_sb.get()):Spinbox.get() 返回字符串,按需转整数。self 的属性:self.cat_cb、self.age_scale、self.score_sb 全部挂在 App 实例上,方法通过 self.xxx.get() 统一读取。self,将来拼成一条联系人记录就很自然——这正是为第 11 章 Treeview 表格做铺垫。self:例 9.2 的 on_scale、例 9.3 的 save 都不依赖全局,只通过 self 拿控件取值,结构清晰、易扩展。from 写成不带下划线:ttk.Scale(self.root, from=0, ...) 直接语法报错(from 是关键字)。必须写 from_。state="readonly":用户能往框里乱打字,存进脏数据(如"同事 "多了空格或"外星人")。选值场景务必 state="readonly"。Scale 的 command 回调收到的是字符串:on_scale(self, value) 里 value 是 "20.0" 这类字符串,要做数值运算记得 float()/int() 转换。Spinbox.get() 返回字符串:需要整数要先 int(),否则和别的数拼接会出问题。Combobox.current() 越界:current(99) 超出选项数量不报错但显示空,注意索引别超范围。fg/bg:换颜色用 Style(第 7 章)。values=("北京","上海","广州","深圳")),点按钮把所选城市显示在 Label(提示:state="readonly" + current(0))。orient="vertical" 试一次垂直滑块。from_=0 误写成 from=0,会发生什么?(提示:Python 把 from 当成关键字,from=0 是语法错误,_validate.py 会报 invalid syntax,程序根本跑不起来。)下节预告:第 10 章我们学 Listbox——一次性显示"一列"多条数据(比如所有联系人姓名),并能滚动、能选中、能删除,开始接触"数据 → 界面"的刷新思维。