本书红线(贯穿全教程,回顾):
1. 每个界面都用class App:封装,__init__里建控件,方法即事件回调。
2. 所有控件示例一律写在类里,不用脱离类的扁平脚本。
3. 代码块带if __name__ == "__main__":守卫,可独立运行。
4. 星级:★ 了解 · ★★ 理解 · ★★★ 掌握 · ★★★★ 重点必会。
主线项目:图形界面通讯录管理器,随章节每章新增一个控件慢慢长大。
上节回顾(第 15 章):你学会了用 bind 处理更细的交互——回车提交、鼠标坐标、右键弹菜单、Combobox 虚拟事件,还补齐了"回调用 lambda 传参数"的遗留问题。本章用系统原生的标准对话框来"提示、确认、取文件、取色",不再自己造轮子。
本章目标:掌握 tkinter 四大标准对话框模块——messagebox(消息/确认)、filedialog(选文件)、simpledialog(取输入)、colorchooser(取颜色)。重点记清每个函数的"签名与返回值",并学会把弹窗结果直接驱动 self 的状态与界面。OOP 落点:弹窗调用写在 App 的方法里,结果用来决定"要不要真删""把内容读进 Text"等。
是什么:tkinter 内置了一组调用操作系统原生弹窗的函数。它们不是你自己画的窗口,而是系统风格的对话框,体验统一、代码极短。
四个模块各管一类场景:
| 模块 | 干啥用 | 典型场景 |
|---|---|---|
messagebox | 提示 / 警告 / 错误 / 确认 | "保存成功""确定删除吗" |
filedialog | 打开 / 保存 / 选目录 | 打开通讯录文件、导出 |
simpledialog | 取一行输入 / 数字 | 让用户输入姓名、年龄 |
colorchooser | 选颜色 | 设置标签颜色 |
为什么要"用现成的":自己做一个确认框要建窗口、加按钮、写逻辑,还不一定好看。标准对话框一行搞定,且和用户的其它软件风格一致。
📌 导入方式:from tkinter import messagebox, filedialog, simpledialog, colorchooser。它们是 tkinter 的子模块,不是tk.xxx。
本章最核心的表——记不住返回值,代码必错。下面每个函数都标注"签名"和"点了不同按钮分别返回什么"。
| 函数 | 签名 | 返回值 | 说明 |
|---|---|---|---|
askyesno(title, msg) | (标题, 提示) | True / False | "是/否"提问 |
askokcancel(title, msg) | (标题, 提示) | True / False | "确定/取消" |
askquestion(title, msg) | (标题, 提示) | 字符串 'yes' / 'no' | 注意返回字符串 |
askretrycancel(title, msg) | (标题, 提示) | True / False | "重试/取消" |
showinfo(title, msg) | (标题, 提示) | 字符串 'ok' | 仅提示,无选择 |
showwarning(title, msg) | (标题, 提示) | 字符串 'ok' | 警告 |
showerror(title, msg) | (标题, 提示) | 字符串 'ok' | 错误 |
⚠️ 易混点:askyesno返回布尔True/False;askquestion返回字符串'yes'/'no'。别拿askyesno的返回值去和'yes'比,永远不相等。
| 函数 | 签名 | 返回值 |
|---|---|---|
askopenfilename(filetypes=[...], parent=...) | 见下 | 选中→路径字符串;取消→空字符串 "" |
asksaveasfilename(filetypes=[...], parent=...) | 同上 | 选中→路径;取消→ "" |
askdirectory(parent=...) | 只传 parent | 选中→目录路径;取消→ "" |
filetypes 写法:[("文本文件", ".txt"), ("所有文件", ".*")],是(描述, 通配符) 的列表。
| 函数 | 签名 | 返回值 |
|---|---|---|
askstring(title, prompt) | (标题, 提示语) | 输入→字符串;取消→ None |
askinteger(title, prompt) | (标题, 提示语) | 输入→ int;取消→ None |
askfloat(title, prompt) | (标题, 提示语) | 输入→ float;取消→ None |
| 函数 | 签名 | 返回值 |
|---|---|---|
askcolor() | 可传 parent= 等 | 选色→ ((r, g, b), "#rrggbb") 元组;取消→ None |
返回值是个双层元组:第一层是 RGB 整数三元组,第二层是十六进制颜色串。通常直接拿第二个元素 "#rrggbb" 用。
本书红线:示例一律写在类里。下面每个例子都完整可运行,并逐个演示返回值。
演示 askyesno(返回布尔)与 showinfo/showwarning/showerror(仅提示)。
# 第16章 例16.1:用 messagebox 做"确认删除"和"结果提示"
import tkinter as tk
from tkinter import messagebox
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器 - 消息框")
tk.Button(self.root, text="删除联系人", command=self.try_delete).pack(padx=10, pady=5)
tk.Button(self.root, text="仅提示信息", command=self.show_info).pack(padx=10, pady=5)
tk.Button(self.root, text="显示警告", command=self.show_warn).pack(padx=10, pady=5)
tk.Button(self.root, text="显示错误", command=self.show_err).pack(padx=10, pady=5)
def try_delete(self):
# askyesno 返回 True(是) 或 False(否) —— 直接用 if 判断
ok = messagebox.askyesno("确认", "确定要删除该联系人吗?")
if ok: # ok 是布尔值
messagebox.showinfo("完成", "已删除。") # 仅提示,返回 'ok' 但不关心
else:
messagebox.showinfo("取消", "未删除。")
def show_info(self):
messagebox.showinfo("提示", "这是一条普通信息。") # 仅提示
def show_warn(self):
messagebox.showwarning("警告", "该联系人已存在!") # 黄色警告图标
def show_err(self):
messagebox.showerror("错误", "保存失败:磁盘已满。") # 红色错误图标
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
ok = messagebox.askyesno("确认", "确定要删除该联系人吗?"):返回值直接是 True/False,所以 if ok: 就能判断用户选了"是"还是"否"。这就是"弹窗结果驱动界面"——只有确认才走删除逻辑。showinfo/showwarning/showerror:用户只能点"确定",函数只起"通知"作用,返回值一般忽略。三个函数区别在于图标与语气(信息 / 警告 / 错误)。用 askopenfilename 选一个文本文件,再用 tk.Text 显示其内容。注意 parent= 让弹窗居中于主窗。
# 第16章 例16.2:用 filedialog 选文本文件,读入 Text 显示
import tkinter as tk
from tkinter import filedialog
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器 - 打开文件")
tk.Button(self.root, text="打开文件", command=self.open_file).pack(padx=10, pady=5)
# tk.Text:多行文本显示区(第16章用它接收文件内容)
self.text = tk.Text(self.root, width=50, height=12)
self.text.pack(padx=10, pady=10)
def open_file(self):
# askopenfilename 返回选中的路径字符串;取消则返回 ""
# parent=self.root 让文件对话框出现在主窗口中央
path = filedialog.askopenfilename(
title="选择一个文本文件",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")],
parent=self.root,
)
if path == "": # 用户取消,path 是空串,直接返回
return
# 读到内容后,先清空 Text,再插入文件内容
with open(path, "r", encoding="utf-8") as f:
content = f.read()
self.text.delete("1.0", tk.END) # 清空原有内容("1.0" 是 Text 起点)
self.text.insert(tk.END, content) # 把文件内容写进 Text
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
path = filedialog.askopenfilename(filetypes=[("文本文件", ".txt"), ("所有文件", ".*")], parent=self.root):签名重点——filetypes 是"描述+通配符"的列表;parent=self.root 让弹窗居中于主窗(忘传会飘在屏幕角落,见 16.6)。if path == "": return:取消时返回的是 "" 不是 None,所以用空串判断。判空即可安全跳过。self.text.delete("1.0", tk.END):Text 的清空方式——"1.0" 表示第 1 行第 0 列(Text 行号从 1 起),tk.END 是末尾。演示 askstring(返回字符串或 None)与 askcolor(返回双层元组或 None),并把颜色应用到 Label。
# 第16章 例16.3:用 simpledialog 取姓名,用 colorchooser 取颜色并上色
import tkinter as tk
from tkinter import simpledialog, colorchooser
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器 - 输入与取色")
self.label = tk.Label(self.root, text="(未命名)", font=("微软雅黑", 14), width=20)
self.label.pack(padx=10, pady=10)
tk.Button(self.root, text="输入姓名", command=self.ask_name).pack(padx=10, pady=4)
tk.Button(self.root, text="选择颜色", command=self.choose_color).pack(padx=10, pady=4)
def ask_name(self):
# askstring 返回用户输入的字符串;取消返回 None
name = simpledialog.askstring("输入", "请输入联系人姓名:", parent=self.root)
if name: # None 或空串都视为未输入
self.label.config(text=name) # 把姓名显示到 Label
def choose_color(self):
# askcolor 返回 ((r,g,b), "#rrggbb") 或 None
result = colorchooser.askcolor(parent=self.root)
if result: # 没取消才有值
hex_color = result[1] # 取第二层:十六进制颜色串
self.label.config(fg=hex_color) # 用选中的颜色给文字上色
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
name = simpledialog.askstring("输入", "请输入联系人姓名:", parent=self.root):取消时返回 None(不是空串!和 filedialog 不同),所以用 if name: 既排除了 None 也排除了空输入。result = colorchooser.askcolor(parent=self.root):返回 ((r, g, b), "#rrggbb")。result[1] 是 "#rrggbb" 十六进制串,直接喂给 config(fg=...) 上色。取消时 result 为 None,if result: 跳过。把前面所学串起来——列表里选中项,删除前弹确认框;"打开"按钮选文件读进 Text。完整可运行。
# 第16章 例16.4:通讯录综合——删除确认 + 打开文件进 Text
import tkinter as tk
from tkinter import messagebox, filedialog
class App:
def __init__(self, root):
self.root = root
self.root.title("通讯录管理器")
# 左边:联系人列表
self.listbox = tk.Listbox(self.root, height=8, width=20)
self.listbox.pack(side=tk.LEFT, padx=10, pady=10)
for n in ("张三", "李四", "王五"):
self.listbox.insert(tk.END, n)
# 右边:按钮 + 详情 Text
frm = tk.Frame(self.root)
frm.pack(side=tk.LEFT, padx=10, pady=10)
tk.Button(frm, text="删除选中", command=self.delete_selected).pack(pady=4)
tk.Button(frm, text="打开文件", command=self.open_file).pack(pady=4)
self.text = tk.Text(frm, width=30, height=8)
self.text.pack(pady=4)
def delete_selected(self):
sel = self.listbox.curselection() # 返回选中项索引元组
if not sel: # 没选任何项
messagebox.showwarning("提示", "请先选中一个联系人。")
return
name = self.listbox.get(sel[0])
# ★确认删除才真删:askyesno 返回布尔
if messagebox.askyesno("确认删除", f"确定删除 {name} 吗?"):
self.listbox.delete(sel[0])
messagebox.showinfo("完成", f"已删除 {name}。")
def open_file(self):
path = filedialog.askopenfilename(
filetypes=[("文本文件", "*.txt")], parent=self.root)
if path == "": # 取消返回空串
return
with open(path, "r", encoding="utf-8") as f:
content = f.read()
self.text.delete("1.0", tk.END)
self.text.insert(tk.END, content)
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()逐行说明:
self.listbox.curselection():拿到当前选中项的索引(可能是多个,元组形式)。空元组表示没选。if messagebox.askyesno("确认删除", ...)::只有用户点"是"(True),才执行真正删除——这就是"弹窗结果驱动状态"的标准范式,避免误删。self.text。App 方法里:def delete_selected(self): 内部调用 messagebox.askyesno(...),拿到返回值后,直接操作 self.listbox、self.text 这些实例属性。弹窗不是孤立的,它是 App 行为的一部分。self 状态:确认框的 True/False 决定"删不删";文件框的路径决定"读不读";取色框的颜色决定 Label 的长相。所有 UI 变化都收口在类的状态上。open_file、delete_selected 都可在菜单(第 17 章)、工具栏等多处被调用,因为逻辑已经封装成方法。一句话:"标准对话框是 App 的一个方法调用,它的返回值就是决定界面下一步怎么变的依据。"
parent=self.root:弹窗会跑到屏幕某个角落(甚至任务栏后面),不在主窗中央,用户以为程序卡了。任何对话框函数都建议带 parent=。askyesno 的返回值当字符串比:askyesno 返回 True/False,askquestion 才返回 'yes'/'no'。写 if messagebox.askyesno(...) == 'yes': 永远不成立。filedialog 取消返回 "" 而非 None:别用 if path is None 判断,要用 if path == "": 或 if not path:。simpledialog / colorchooser 取消返回 None:这两个和 filedialog 相反,取消是 None,用 if result: 判空最稳妥(同时涵盖 None 和空串)。colorchooser 返回值是双层元组:((r,g,b), "#rrggbb"),直接拿 result[1] 用十六进制串,别误用 result[0](那是 RGB 整数)。。askstring 拿到空输入仍是字符串 "":if name: 能同时排除 None 与空串,比 if name is not None: 更严谨。Text 清空用 delete("1.0", tk.END):不是 clear()(那是 Entry 的 delete(0, tk.END) 风格),Text 的行号从 1 起。simpledialog.askstring 取姓名,把姓名插入 Listbox;若用户取消(返回 None)则不添加。messagebox.askyesno 确认,确认才删;没选中项时用 showwarning 提示。colorchooser.askcolor,把返回的十六进制颜色串应用到窗口 self.root.config(bg=...)。取消时不做任何改变。parent=self.root,用户体验会怎样?若把 if path == "": 误写成 if path is None:,用户点"取消"后程序会怎样?(提示:取消返回的是 "" 不是 None,is None 判断永远为 False,于是会拿空串去 open("") 而报错——印证 16.5 的 🔴🟡 易错点。)下节预告:第 17 章我们正式做菜单(Menu)——菜单栏(文件/编辑)、列表右键菜单、以及 Menubutton 下拉按钮,让你的通讯录拥有专业软件的"菜单感"。