本书红线(贯穿全教程,回顾):
1. 每个界面都用class App:封装,__init__里建控件,方法即事件回调。
2. 所有控件示例一律写在类里,不用脱离类的扁平脚本。
3. 代码块带if __name__ == "__main__":守卫,可独立运行。
4. 星级:★ 了解 · ★★ 理解 · ★★★ 掌握 · ★★★★ 重点必会。
主线项目:图形界面通讯录管理器,随章节每章新增一个控件慢慢长大。
上节回顾(第 7 章):你学会了用 ttk 主题控件和 Style 给整个界面"换肤",让按钮、标签变得更好看、风格统一。本章我们给通讯录加上"勾选"和"单选"能力——也就是 Checkbutton(复选框) 和 Radiobutton(单选钮),让用户做"多选"和"单选"。
本章目标:认识两个与"选择状态"相关的控件——Checkbutton(复选框,可多选/开关) 和 Radiobutton(单选钮,一组里只能选一个)。重点是学会用"变量类"(BooleanVar / StringVar / IntVar)把"界面上的勾选状态"和"程序里的数据"绑起来:勾上就是 True,没勾就是 False;单选里选了哪个,变量就存哪个 value。OOP 落点:这些变量和控件都收在 self 下,点按钮时通过 var.get() 读取用户选择。
是什么:
Checkbutton(复选框):一个小方框 + 文字。点一下打勾(选中),再点一下取消。它代表一个可以独立开/关的选项(如"是否收藏")。同一界面里可以有多个互相不影响的复选框,用户想勾几个勾几个。Radiobutton(单选钮):一组小圆圈,同一组里只能选其中一个。选了 A 会自动取消 B 和 C。它代表"从几个互斥选项里挑一个"(如性别:男/女;学历:本科/硕士/博士)。适用场景:
| 控件 | 典型场景 | 通讯录里的用法 |
|---|---|---|
| Checkbutton | 是否收藏、是否启用、多个可叠加的偏好 | "是否收藏此联系人" |
| Radiobutton | 性别、会员等级、单选分类等互斥选项 | "性别:男 / 女" |
不适用:选项特别多(十几个)时应改用第 9 章的 Combobox(下拉);选项可以连续变化用第 9 章的 Scale(滑块)。
Checkbutton 与 Radiobutton 都来自 ttk,常用选项如下(官方文档:tkinter.ttk.Checkbutton / tkinter.ttk.Radiobutton):
| 选项 | 作用 | 常用取值 | 重要度 |
|---|---|---|---|
text | 控件旁显示的文字 | "男" / "是否收藏" | ★★★★ |
variable | 绑定的变量(状态存这里) | tk.BooleanVar() / tk.StringVar() / tk.IntVar() | ★★★★ |
onvalue | 选中时变量变成的值(Checkbutton) | True(默认 1) | ★★★ |
offvalue | 未选中时变量变成的值(Checkbutton) | False(默认 0) | ★★★ |
value | 选中时变量变成的值(Radiobutton) | "男" / 1 等,每个钮不同 | ★★★★ |
command | 状态变化时调用的回调 | self.on_change(方法名) | ★★★ |
state | 可用 / 禁用 | "normal" / "disabled" | ★★ |
📌 两个控件最大的区别在variable的"共用":
- Checkbutton 每个框可以各自绑一个变量(或同一个变量代表一个开关)。
- Radiobutton 一组必须共用同一个variable,靠不同的value来区分"选的是哪一个"。忘了共用同一个变量,单选就失灵了(见 8.6 易错清单)。
Checkbutton / Radiobutton 本身方法不多,真正的"取值/设值"都在绑定的变量类上:
| 对象 / 方法 | 作用 | 示例 |
|---|---|---|
var.get() | 读取变量当前值 | self.gender_var.get() → "男" |
var.set(值) | 设置变量值(界面同步变化) | self.gender_var.set("女") |
tk.BooleanVar() | 布尔变量(True/False) | self.fav = tk.BooleanVar() |
tk.StringVar() | 字符串变量 | self.gender = tk.StringVar() |
tk.IntVar() | 整数变量 | self.level = tk.IntVar() |
widget.invoke() | 程序模拟"点一下"(切换状态并触发 command) | cb.invoke() |
关键认知:"用户看到的勾选状态"和"程序里的数据"通过variable合二为一。你不用去读控件,直接var.get()读数据、var.set()写数据,界面会自动跟着变。这是 GUI 里"数据驱动界面"思想的雏形。
本书红线:示例一律写在 class App 里。下面每个例子都可直接运行(本机会弹出窗口)。
最简单的复选框——一个开关,勾上代表"收藏"。
# 第8章 例8.1:用 Checkbutton 实现"是否收藏"开关
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器")
# ① 创建一个布尔变量,专门记录"是否收藏";初值 False(未勾选)
self.fav_var = tk.BooleanVar(value=False)
# ② 创建 Checkbutton:variable 绑到 fav_var,勾上=onvalue(True),没勾=offvalue(False)
self.fav_cb = ttk.Checkbutton(
self.root,
text="是否收藏",
variable=self.fav_var, # 状态写进 fav_var
onvalue=True, # 勾选时变量变 True
offvalue=False, # 取消勾选时变量变 False
command=self.on_toggle, # 状态变化时调用的方法
)
self.fav_cb.pack(padx=20, pady=10)
# ③ 一个 Label 用于把当前状态显示出来
self.label = ttk.Label(self.root, text="收藏状态:未收藏")
self.label.pack(padx=20, pady=10)
def on_toggle(self):
# ④ 通过 var.get() 读取勾选状态:返回 True 或 False
if self.fav_var.get():
self.label.config(text="收藏状态:已收藏 ★")
else:
self.label.config(text="收藏状态:未收藏")
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明(重点看 ①②④):
self.fav_var = tk.BooleanVar(value=False):布尔变量,True/False 正好对应"勾/不勾"。variable=self.fav_var + onvalue=True + offvalue=False:这就是 Checkbutton 的"灵魂三连"——把界面勾选状态映射到变量值。self.fav_var.get():读取当前是勾还是没勾。返回 True/False,直接拿来判断。单选钮的关键:一组共用同一个 variable,每个钮 value 不同。
# 第8章 例8.2:用 Radiobutton 实现"性别:男/女"单选
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器")
# ① 一个字符串变量,记录"当前选中的性别";value 用字符串,初值"男"
self.gender_var = tk.StringVar(value="男")
# ② 男、女两个单选钮【共用同一个 gender_var】,但 value 不同
self.rb_male = ttk.Radiobutton(
self.root,
text="男",
variable=self.gender_var, # 和下面"女"共用同一个变量
value="男", # 选中"男"时,gender_var 变成 "男"
command=self.show_gender,
)
self.rb_female = ttk.Radiobutton(
self.root,
text="女",
variable=self.gender_var, # 必须是同一个 variable 才能互斥
value="女", # 选中"女"时,gender_var 变成 "女"
command=self.show_gender,
)
self.rb_male.pack(padx=20, pady=5)
self.rb_female.pack(padx=20, pady=5)
self.label = ttk.Label(self.root, text="性别:男")
self.label.pack(padx=20, pady=10)
def show_gender(self):
# ③ var.get() 取的就是当前被选中钮的 value
self.label.config(text=f"性别:{self.gender_var.get()}")
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明(重点看 ②):
Radiobutton 的 variable 都指向 self.gender_var——这正是"互斥单选"的奥秘。tkinter 发现同一变量被多个单选钮共享时,会自动保证"选一个、取消其它"。value 不同("男" / "女")。点"女",gender_var 被设成 "女",同时"男"自动取消。self.gender_var.get() 直接返回 "男" 或 "女"。把前面两个控件放进同一个通讯录添加表单,点"确认"按钮把选择显示在 Label。
# 第8章 例8.3:通讯录表单里同时用 Checkbutton(收藏) + Radiobutton(性别)
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器 - 新增联系人")
# 姓名输入框(用 ttk.Entry,回忆第4章;这里只取它的值用)
self.name_var = tk.StringVar()
ttk.Label(self.root, text="姓名:").pack(padx=20, pady=(10, 0))
self.name_entry = ttk.Entry(self.root, textvariable=self.name_var)
self.name_entry.pack(padx=20, pady=5)
# ① Checkbutton:是否收藏(布尔变量)
self.fav_var = tk.BooleanVar(value=False)
self.fav_cb = ttk.Checkbutton(
self.root, text="是否收藏",
variable=self.fav_var, onvalue=True, offvalue=False,
)
self.fav_cb.pack(padx=20, pady=5, anchor="w")
# ② Radiobutton:性别(字符串变量,男/女共用)
self.gender_var = tk.StringVar(value="男")
ttk.Radiobutton(self.root, text="男",
variable=self.gender_var, value="男").pack(padx=20, pady=2, anchor="w")
ttk.Radiobutton(self.root, text="女",
variable=self.gender_var, value="女").pack(padx=20, pady=2, anchor="w")
# ③ 确认按钮:点击后汇总显示
self.btn = ttk.Button(self.root, text="确认", command=self.confirm)
self.btn.pack(padx=20, pady=10)
# ④ 结果显示 Label
self.result = ttk.Label(self.root, text="(等待提交)")
self.result.pack(padx=20, pady=10)
def confirm(self):
name = self.name_var.get() or "(未填姓名)"
# ⑤ 直接读两个变量,拼成一句话
fav = "已收藏" if self.fav_var.get() else "未收藏"
gender = self.gender_var.get()
self.result.config(text=f"姓名:{name} 性别:{gender} {fav}")
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
self.fav_var 是布尔开关,self.fav_cb 勾选即 True。Radiobutton 再次共用 self.gender_var,value 分 "男"/"女"。confirm 里用 self.fav_var.get()(True/False)和 self.gender_var.get()("男"/"女")读取用户选择——不用碰控件本身,只读变量,这就是绑定的好处。本章把"界面状态"正式纳入了对象的属性体系:
self 的成员:self.fav_var、self.gender_var 和 self.fav_cb、self.rb_male 一样,都挂在 App 实例上。它们不是全局变量,而是"这个窗口对象"的一部分。var.get() 读状态:像例 8.3 的 confirm,它不关心"哪个控件被点",只关心"数据是什么"。这种"方法读 self 上的变量"的模式,让界面逻辑和数据逻辑解耦。variable= 让"控件显示"和"变量值"自动同步——你 var.set() 界面变,用户点界面 var.get() 拿到新值。后面章节的 Listbox、Treeview 也会用类似的"变量/数据驱动界面"思维。variable:每个 Radiobutton 都 variable=self.xxx 但各自新建了不同变量,结果"几个钮能同时选中",失去单选意义。正确做法:一组里所有钮指向同一个变量对象。value 类型前后不一致:比如变量是 IntVar 却在某个钮写 value="男"(字符串),get() 就取不到预期值或类型混乱。统一类型(都字符串或都整数)。StringVar 但 onvalue/offvalue 写了 True/False:StringVar 装不下布尔,会出问题。布尔开关用 BooleanVar + onvalue=True/offvalue=False 最省心。command=self.on_toggle() 写成带括号:会立即执行而不是点击才执行(老问题,见第 3 章)。var.get(),拿到的是初值。需要在回调(点击后)里读,才是用户真正选的。fg/bg:换颜色要用 Style(第 7 章),给 Checkbutton/Radiobutton 直接传 fg 无效。var.get() 判断真假)。IntVar,value 分别用 1/2/3,点按钮把选中的数字显示在 Label。variable 写成两个不同的 StringVar(各绑一个),运行后点击会怎样?(提示:两个钮各自独立,可以同时"都选中",不再是单选。)下节预告:第 9 章我们学更省空间的"选择/调数"控件——Combobox(下拉框选分类)、Scale(滑块调年龄)、Spinbox(上下箭头调数字),选项一多就用它们替代满屏的单选钮。