本书红线(贯穿全教程,回顾):
1. 每个界面都用class App:封装,__init__里建控件,方法即事件回调。
2. 所有控件示例一律写在类里,不用脱离类的扁平脚本。
3. 代码块带if __name__ == "__main__":守卫,可独立运行。
4. 星级:★ 了解 · ★★ 理解 · ★★★ 掌握 · ★★★★ 重点必会。
主线项目:图形界面通讯录管理器,随章节每章新增一个控件慢慢长大。
上节回顾(第 2 章):你认识了第一个控件 Label,学会了在类里创建它、设置字体颜色与边框、用 config() 在运行时改它的文字,并记住了"界面上的每个控件都是 self 的一个属性"。本章认识第二个控件——Button(按钮),它是第一个"能交互"的控件。
本章目标:认识第二个控件——Button(按钮)。它是第一个"能交互"的控件:你点它,它就干活。本章重点是全书的 OOP 核心范式——"方法即回调":把"按钮被点击"这件事,交给类里的一个方法来处理。
是什么:Button 是按钮控件。它在窗口上显示一个可点击的区域(通常带文字或图标),用户一点击,就触发一个动作。
适用场景:
和 Label 的区别:Label 只能"看",Button 能"点"——点一下会去调用你指定的函数(叫"回调函数")。
下面是 Button 最常用的选项(来自官方 tkinter.Button 文档):
| 选项 | 作用 | 常用取值 | 重要度 |
|---|---|---|---|
text | 按钮上显示的文字 | "添加" | ★★★★ |
command | 点击时调用的函数(回调) | self.on_click(方法名,不加括号) | ★★★★ |
image | 按钮上的图片(需 PhotoImage) | tk.PhotoImage(...) | ★ |
compound | 图文混排时图片相对文字位置 | "left"/"top" 等 | ★ |
state | 按钮状态 | "normal"(可用,默认)/"disabled"(禁用灰掉) | ★★★ |
width / height | 按钮宽/高(字符数) | 整数 | ★★ |
fg / bg | 文字色 / 背景色 | 颜色字符串 | ★★ |
font | 字体字号 | 元组 | ★★ |
relief | 边框样式 | "raised"(默认)/"flat" 等 | ★★ |
default | 是否为默认按钮(视觉提示) | "active"/"normal" | ★ |
📌 本章最关键的选项就是 command——它决定了"点这个按钮会发生什么"。下面 3.5 节会把它讲透。
| 方法 | 作用 | 示例 |
|---|---|---|
config() / configure() | 运行时改选项(如改文字、禁用) | btn.config(state="disabled") |
invoke() | 编程方式"模拟点击"(不真用鼠标点,也触发 command) | btn.invoke() |
flash() | 让按钮闪一下(视觉反馈) | btn.flash() |
invoke()很实用:写自动化测试、或想"按回车等于点按钮"时(后续章节事件绑定会用到),可以直接btn.invoke()触发回调,而不用真去点。
这是本书第一个有交互的程序:点按钮,窗口里的文字就变。
# 第3章 例3.1:点击按钮,改变 Label 显示的文字
import tkinter as tk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器")
# ① 先放一个 Label 作为"被改变"的对象,存进 self
self.msg = tk.Label(self.root, text="还没点呢", font=("微软雅黑", 14))
self.msg.pack(padx=20, pady=10)
# ② 创建一个 Button,command 指定"点击后调用的方法"
# 注意:command=self.on_click 是"方法名",不要加括号!
self.btn = tk.Button(
self.root,
text="点我",
command=self.on_click, # 点击 → 调用 self.on_click 方法
)
self.btn.pack(padx=20, pady=10)
# ③ 被调用的方法(回调函数):第一个参数永远是 self
def on_click(self):
self.msg.config(text="你点了我!") # 通过 self.msg 改那个 Label 的文字
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明(重点看 ②③):
command=self.on_click:这是本章的灵魂。它告诉按钮"被点击时,去调用 self.on_click 这个方法"。注意写的是 self.on_click(方法名),不是 self.on_click()(带括号会变成"立刻执行"而不是"点击才执行")。def on_click(self)::这是 App 类里的一个方法。按钮被点 → tkinter 自动调用它 → 里面 self.msg.config(text="你点了我!") 把 Label 文字改掉。把"点击"和"数据"结合起来——每点一次,计数 +1 并刷新显示。这是 GUI 程序最典型的模式(点一下 → 改数据 → 刷新界面):
# 第3章 例3.2:点击按钮,计数器加 1 并显示
import tkinter as tk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器")
self.count = 0 # ① 用一个实例属性保存"计数"状态
self.label = tk.Label(self.root, text=f"已点击 {self.count} 次",
font=("微软雅黑", 14))
self.label.pack(padx=20, pady=10)
self.btn = tk.Button(self.root, text="加 1", command=self.add_one)
self.btn.pack(padx=20, pady=10)
def add_one(self): # ② 点击时调用
self.count += 1 # ③ 改数据:计数 +1
self.label.config(text=f"已点击 {self.count} 次") # ④ 刷界面:把新数据显示出来
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
self.count = 0:计数存在 self 上,整个 App 对象都能访问。GUI 程序里,"数据状态"通常就挂在 self 上。self.count += 1:点一次,数据 +1。self.label.config(text=...):数据变了,立刻用 config 把界面同步过去。这条"改数据 → 刷界面"的链路,是几乎所有 GUI 程序的核心循环,请务必吃透。# 第3章 例3.3:点击后把按钮禁用,避免重复点击
import tkinter as tk
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器")
self.btn = tk.Button(self.root, text="提交", command=self.submit)
self.btn.pack(padx=20, pady=20)
def submit(self):
self.btn.config(state="disabled", text="已提交") # 禁用并改文字
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()说明:state="disabled" 让按钮变灰、不可点;state="normal" 恢复可用。常用于"提交后防止重复提交"。
这是 tkinter + 面向对象结合处最重要的一句话,请记住:
按钮的command指向类里的一个方法;用户点击 → tkinter 自动调用这个方法。这个方法通过self拿起界面上的其它控件,去改它们、去读数据。
为什么这叫"回调"(callback):你写 command=self.on_click 时,并没有马上调用 on_click——你只是"登记"说"将来有人点按钮,请你帮我调用它"。点击发生时,tkinter 反过来"回调"你的方法。这种"你登记、它回头调用"的模式就叫回调。
对比:扁平函数版 vs 类版(让你看清类的优势)
扁平写法(不用类):
# 不推荐:控件和函数散落在全局,函数要自己找全局变量 label
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text="hi")
label.pack()
def on_click():
label.config(text="clicked") # 得靠全局变量 label,多个按钮时易乱
btn = tk.Button(root, text="点", command=on_click)
btn.pack()
root.mainloop()类写法(本书推荐):
# 推荐:所有控件挂 self,方法通过 self 互相关联,结构清晰
import tkinter as tk
class App:
def __init__(self, root):
self.root = root
self.label = tk.Label(root, text="hi")
self.label.pack()
self.btn = tk.Button(root, text="点", command=self.on_click)
self.btn.pack()
def on_click(self):
self.label.config(text="clicked") # 通过 self.label 直接拿,不用全局变量
root = tk.Tk(); App(root); root.mainloop()类的优势一目了然:控件都收在 self 下,方法之间用 self.xxx 沟通,不用到处传全局变量。当界面有几十个控件、十几个方法时,这种组织方式能救命。
command=self.on_click 误写成 command=self.on_click()(多了括号):后果是程序一启动就立刻执行一次 on_click(因为带括号表示"现在就调用"),而且按钮点击时反而什么都不做。记住:command 后面是"方法名",绝不加括号。self 参数:def on_click(): 会报错 TypeError: on_click() takes 0 positional arguments but 1 was given——因为 tkinter 调用时会自动传 self。正确写 def on_click(self):。command=self.on_click(参数):又会变成立即执行。正确做法是用 lambda:command=lambda: self.on_click(参数)。lambda 细节留到后续章节事件绑定正式讲,本章先用"无参方法"即可。invoke() 和真实点击混用导致重复触发:invoke() 会真的调用 command,别在回调里又调一次 invoke 造成死循环。width 限制或改用 wraplength(部分平台支持)。state 设为 "disabled",文字改为"已满分"。self.is_on 布尔变量记录状态,在回调里取反)。command=self.on_click 改成 command=self.on_click(),运行时会发生什么?(提示:启动即报错或立即执行一次,且点击无效——印证 3.6 节的 🔴 易错点。)本章小结:你现在已经能让程序"响应点击"了——这是 GUI 程序的灵魂。下一章我们学怎么把 Label、Button 这些控件排得整整齐齐(布局管理器),目前它们只是简单地上下叠着。