5. 事件与变量:让界面动起来
这是什么:事件绑定(bind)让控件在"被点击、被按键、被划过"时执行你的函数;Tkinter 变量(StringVar 等)是绑在控件上的特殊盒子——程序改它,界面自动变;界面改它,程序立刻知道。
为什么用:上一章的界面是"死"的——按钮只认 command,输入框要你手动 get()。学会这两样,界面就活了:回车也能提交、勾选状态直接读、输入一个字标签就同步一个字、窗口里还能走一个秒表。
如何用:先用 bind 把"按回车"接进上一章的打招呼机,再用 StringVar 做一个实时字数统计。
bind:什么事件、哪个控件、干什么
import tkinter as tk
def on_key(event):
label.config(text=f"你按下了:{event.char!r}")
root = tk.Tk()
root.title("键盘监视器")
label = tk.Label(root, text="按任意键", font=("", 16))
label.pack(pady=40)
root.bind("<Key>", on_key)
root.bind("<Button-1>", lambda e: label.config(text="鼠标被点了"))
root.mainloop()
你会看到:按 a 显示"你按下了:'a'";点窗口空白处显示"鼠标被点了"。
| 事件串 | 触发时机 |
|---|---|
<Key> / <Return> / <Escape> |
任意键 / 回车 / Esc(<KeyPress-a> 精确到 a 键) |
<Button-1> / <Button-3> |
鼠标左键 / 右键 |
<Motion> / <Enter> / <Leave> |
拖动 / 指针进入 / 离开控件 |
回调函数必须收一个参数(那个事件对象),常用字段:event.x、event.y(坐标)、event.char(按键字符)。一行小逻辑不想单独写函数时,用 lambda e: ... 顶替(e 就是事件参数)。
Tkinter 变量:界面和程序双向同步
普通 Python 变量和界面是两路人:你改了变量,标签不知道;用户改了输入框,你也不清楚。特殊变量专门解决这个问题:
import tkinter as tk
root = tk.Tk()
root.title("实时字数统计")
root.geometry("320x140")
text_var = tk.StringVar()
agree_var = tk.BooleanVar(value=False)
def update_count(*_):
n = len(text_var.get().strip())
count_label.config(text=f"已输入 {n} 字")
agree_label.config(text="已同意协议" if agree_var.get() else "还没同意")
entry = tk.Entry(root, textvariable=text_var) # 绑定!
entry.pack(pady=8)
entry.bind("<KeyRelease>", update_count)
cb = tk.Checkbutton(root, text="同意用户协议", variable=agree_var,
command=update_count)
cb.pack()
count_label = tk.Label(root, text="已输入 0 字")
count_label.pack()
agree_label = tk.Label(root, text="还没同意", fg="gray")
agree_label.pack()
root.mainloop()
你会看到:输入框每敲一个字,下面"已输入 N 字"实时跳;勾上/取消勾选框,最后一行在"已同意/还没同意"间切换。
| 变量类型 | 配对的控件属性 | 读值 | 写值 |
|---|---|---|---|
tk.StringVar() |
textvariable= |
.get() |
.set("新文本") |
tk.IntVar() / DoubleVar |
数值型输入/单选 | .get() |
.set(5) |
tk.BooleanVar() |
勾选框 variable= |
.get() |
.set(True) |
记忆口诀:特殊变量两头翘——textvariable 连输入,variable 连勾选;一头动,另一头自动知道。
实时联动的正规写法:trace
上面的例子靠 <KeyRelease> 事件凑效,粘贴输入就不灵了。Tkinter 变量自带变化回调才是正路:
text_var.trace_add("write", lambda *_: update_count())
变量只要被写入(不管来自键盘、粘贴还是代码 .set()),回调必跑。*_ 表示"来几个参数我都接住但不用"。
after:给界面装个闹钟
import tkinter as tk
root = tk.Tk()
clock = tk.Label(root, text="", font=("", 24))
clock.pack(pady=30)
def tick():
import time
clock.config(text=time.strftime("%H:%M:%S"))
root.after(1000, tick) # 1000 毫秒后再叫自己一次
tick() # 启动第一次
root.mainloop()
你会看到:一个 24 小时制秒级时钟,永远在走。
after(毫秒, 函数) 是"N 毫秒后让事件循环跑一次这个函数"。它不阻塞界面——这正是它存在的意义:在 mainloop 里你永远不要用 time.sleep(),整个界面会冻住 1 秒不响应;要延时,一律 after。
记忆口诀:bind 认事件、变量认双向、trace 认变化、after 认闹钟;sleep 一响,界面冻僵。
动手改
- 给打招呼机加上:输入框按回车 = 点按钮(提示:
entry.bind("<Return>", lambda e: greet())) - 把字数统计里的
<KeyRelease>换成trace_add,然后从记事本粘贴一段长文本进输入框——验证两种写法的差别 - 用
after做一个"番茄钟":点按钮开始 25:00 倒计时,倒数到 0 标签变红(提示:after(1000, ...)+IntVar存秒数)
下一章:菜单、对话框与多窗口 🐸