第17天-图形界面
哪吒 2023/6/15
# 第17天-图形界面
# 学习目标
通过第17天的学习,你将掌握:
- GUI编程基础 - 理解图形界面编程的基本概念
- Tkinter框架 - 掌握Python内置GUI库的使用
- 控件使用 - 学会各种GUI控件的应用
- 事件处理 - 理解事件驱动编程模式
- 布局管理 - 掌握界面布局的设计方法
- 高级功能 - 学习菜单、对话框、画布等高级特性
- 实际项目 - 完成完整的GUI应用程序
- 其他GUI框架 - 了解PyQt、wxPython等替代方案
# 一、GUI编程基础
# 1.1 图形界面编程概述
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import threading
import time
def gui_programming_intro():
"""GUI编程基础概念介绍"""
print("=== GUI编程基础概念 ===")
# 1. GUI编程的基本概念
print("\n1. GUI编程基本概念:")
concepts = {
"窗口(Window)": "应用程序的主容器,包含所有其他控件",
"控件(Widget)": "用户界面的基本元素,如按钮、标签、文本框等",
"事件(Event)": "用户操作或系统状态变化,如点击、键盘输入等",
"事件处理器(Handler)": "响应特定事件的函数或方法",
"布局管理器(Layout)": "控制控件在窗口中的位置和大小",
"回调函数(Callback)": "当事件发生时被调用的函数"
}
for concept, description in concepts.items():
print(f" {concept}: {description}")
# 2. 事件驱动编程模型
print("\n2. 事件驱动编程模型:")
event_model = [
"程序启动 -> 创建GUI界面",
"进入事件循环 -> 等待用户操作",
"检测事件 -> 鼠标点击、键盘输入等",
"调用事件处理器 -> 执行相应的处理函数",
"更新界面 -> 刷新显示内容",
"返回事件循环 -> 继续等待下一个事件"
]
for i, step in enumerate(event_model, 1):
print(f" {i}. {step}")
# 3. 常见GUI框架对比
print("\n3. Python GUI框架对比:")
frameworks = {
"Tkinter": {
"优点": ["内置库", "轻量级", "跨平台", "学习简单"],
"缺点": ["外观较老", "控件有限", "性能一般"],
"适用场景": "简单桌面应用、学习GUI编程"
},
"PyQt/PySide": {
"优点": ["功能强大", "外观现代", "控件丰富", "性能优秀"],
"缺点": ["体积较大", "学习复杂", "许可证限制"],
"适用场景": "专业桌面应用、复杂界面"
},
"wxPython": {
"优点": ["原生外观", "功能完整", "跨平台"],
"缺点": ["API复杂", "文档较少", "更新较慢"],
"适用场景": "需要原生外观的应用"
},
"Kivy": {
"优点": ["现代设计", "触摸支持", "移动端支持"],
"缺点": ["学习曲线陡", "桌面体验一般"],
"适用场景": "移动应用、触摸界面"
}
}
for framework, details in frameworks.items():
print(f"\n {framework}:")
print(f" 优点: {', '.join(details['优点'])}")
print(f" 缺点: {', '.join(details['缺点'])}")
print(f" 适用场景: {details['适用场景']}")
# 4. GUI设计原则
print("\n4. GUI设计原则:")
design_principles = [
"一致性: 保持界面元素的一致性",
"简洁性: 避免界面过于复杂",
"可用性: 确保用户能够轻松使用",
"反馈性: 及时给用户操作反馈",
"容错性: 处理用户的错误操作",
"可访问性: 考虑不同用户的需求"
]
for i, principle in enumerate(design_principles, 1):
print(f" {i}. {principle}")
# 运行GUI编程介绍
gui_programming_intro()
# 二、Tkinter基础
# 2.1 基本窗口和控件
import tkinter as tk
from tkinter import ttk
def tkinter_basics_demo():
"""Tkinter基础控件演示"""
print("=== Tkinter基础控件演示 ===")
# 创建主窗口
root = tk.Tk()
root.title("Tkinter基础演示")
root.geometry("600x500")
root.resizable(True, True)
# 设置窗口图标(如果有的话)
# root.iconbitmap('icon.ico')
# 1. 标签控件
print("\n1. 创建标签控件")
# 普通标签
label1 = tk.Label(root, text="欢迎使用Tkinter!",
font=("Arial", 16, "bold"),
fg="blue", bg="lightgray")
label1.pack(pady=10)
# 多行标签
label2 = tk.Label(root,
text="这是一个多行标签\n可以显示多行文本\n支持换行符",
justify=tk.LEFT,
font=("宋体", 12))
label2.pack(pady=5)
# 2. 按钮控件
print("\n2. 创建按钮控件")
def button_click():
print(" 按钮被点击了!")
messagebox.showinfo("提示", "按钮点击事件触发!")
def button_hover_enter(event):
event.widget.config(bg="lightblue")
def button_hover_leave(event):
event.widget.config(bg="SystemButtonFace")
button1 = tk.Button(root, text="点击我",
command=button_click,
font=("Arial", 12),
width=15, height=2)
button1.pack(pady=5)
# 绑定鼠标悬停事件
button1.bind("<Enter>", button_hover_enter)
button1.bind("<Leave>", button_hover_leave)
# 3. 文本输入控件
print("\n3. 创建文本输入控件")
# 单行文本框
tk.Label(root, text="请输入您的姓名:").pack()
entry_var = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_var,
font=("Arial", 12), width=30)
entry.pack(pady=5)
def get_entry_text():
text = entry_var.get()
print(f" 输入的文本: {text}")
if text:
messagebox.showinfo("输入内容", f"您输入的是: {text}")
else:
messagebox.showwarning("警告", "请先输入内容!")
tk.Button(root, text="获取输入", command=get_entry_text).pack(pady=5)
# 4. 多行文本框
print("\n4. 创建多行文本框")
tk.Label(root, text="多行文本输入:").pack()
# 创建文本框和滚动条的框架
text_frame = tk.Frame(root)
text_frame.pack(pady=5, padx=20, fill=tk.BOTH, expand=True)
# 多行文本框
text_widget = tk.Text(text_frame, height=8, width=50,
font=("Consolas", 10),
wrap=tk.WORD)
# 滚动条
scrollbar = tk.Scrollbar(text_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 连接文本框和滚动条
text_widget.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=text_widget.yview)
# 插入默认文本
default_text = """这是一个多行文本框示例。
您可以在这里输入多行文本。
支持滚动条和自动换行。
试试输入一些内容吧!"""
text_widget.insert(tk.END, default_text)
def get_text_content():
content = text_widget.get("1.0", tk.END)
print(f" 文本框内容: {repr(content)}")
lines = content.strip().split('\n')
messagebox.showinfo("文本内容", f"共 {len(lines)} 行,{len(content.strip())} 个字符")
def clear_text():
text_widget.delete("1.0", tk.END)
print(" 文本框已清空")
# 文本操作按钮
button_frame = tk.Frame(root)
button_frame.pack(pady=5)
tk.Button(button_frame, text="获取内容",
command=get_text_content).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="清空内容",
command=clear_text).pack(side=tk.LEFT, padx=5)
# 5. 复选框和单选按钮
print("\n5. 创建复选框和单选按钮")
# 复选框
check_frame = tk.LabelFrame(root, text="兴趣爱好", padx=10, pady=10)
check_frame.pack(pady=10, padx=20, fill=tk.X)
hobbies = ["编程", "阅读", "音乐", "运动", "旅游"]
hobby_vars = {}
for hobby in hobbies:
var = tk.BooleanVar()
hobby_vars[hobby] = var
cb = tk.Checkbutton(check_frame, text=hobby, variable=var)
cb.pack(side=tk.LEFT, padx=5)
def show_selected_hobbies():
selected = [hobby for hobby, var in hobby_vars.items() if var.get()]
if selected:
messagebox.showinfo("选择的爱好", f"您选择了: {', '.join(selected)}")
else:
messagebox.showinfo("选择的爱好", "您没有选择任何爱好")
tk.Button(check_frame, text="查看选择",
command=show_selected_hobbies).pack(side=tk.RIGHT, padx=5)
# 单选按钮
radio_frame = tk.LabelFrame(root, text="选择性别", padx=10, pady=10)
radio_frame.pack(pady=5, padx=20, fill=tk.X)
gender_var = tk.StringVar(value="男")
genders = [("男", "male"), ("女", "female"), ("其他", "other")]
for text, value in genders:
rb = tk.Radiobutton(radio_frame, text=text,
variable=gender_var, value=value)
rb.pack(side=tk.LEFT, padx=10)
def show_selected_gender():
selected = gender_var.get()
messagebox.showinfo("选择的性别", f"您选择了: {selected}")
tk.Button(radio_frame, text="查看选择",
command=show_selected_gender).pack(side=tk.RIGHT, padx=5)
# 窗口关闭事件
def on_closing():
if messagebox.askokcancel("退出", "确定要退出程序吗?"):
print("\n程序正在退出...")
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
print("\n基础控件演示窗口已创建,请查看GUI界面")
print("关闭窗口以继续下一个演示")
# 启动事件循环
root.mainloop()
# 运行Tkinter基础演示
tkinter_basics_demo()
# 2.2 布局管理
import tkinter as tk
from tkinter import ttk
def layout_management_demo():
"""布局管理演示"""
print("=== 布局管理演示 ===")
# 创建主窗口
root = tk.Tk()
root.title("布局管理演示")
root.geometry("800x600")
# 创建笔记本控件来展示不同的布局方式
notebook = ttk.Notebook(root)
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 1. Pack布局演示
print("\n1. Pack布局管理器")
pack_frame = tk.Frame(notebook)
notebook.add(pack_frame, text="Pack布局")
# Pack布局说明
tk.Label(pack_frame, text="Pack布局管理器",
font=("Arial", 14, "bold")).pack(pady=10)
tk.Label(pack_frame,
text="Pack按照添加顺序依次排列控件,支持top、bottom、left、right四个方向",
wraplength=400).pack(pady=5)
# Pack示例
pack_demo_frame = tk.LabelFrame(pack_frame, text="Pack示例", padx=10, pady=10)
pack_demo_frame.pack(pady=10, padx=20, fill=tk.X)
tk.Button(pack_demo_frame, text="Top 1", bg="lightblue").pack(side=tk.TOP, pady=2)
tk.Button(pack_demo_frame, text="Top 2", bg="lightgreen").pack(side=tk.TOP, pady=2)
bottom_frame = tk.Frame(pack_demo_frame)
bottom_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5)
tk.Button(bottom_frame, text="Bottom 1", bg="lightcoral").pack(side=tk.LEFT, padx=2)
tk.Button(bottom_frame, text="Bottom 2", bg="lightyellow").pack(side=tk.RIGHT, padx=2)
middle_frame = tk.Frame(pack_demo_frame)
middle_frame.pack(fill=tk.BOTH, expand=True, pady=5)
tk.Button(middle_frame, text="Left", bg="lightpink").pack(side=tk.LEFT, padx=2)
tk.Button(middle_frame, text="Right", bg="lightgray").pack(side=tk.RIGHT, padx=2)
tk.Label(middle_frame, text="Center Area", bg="white",
relief=tk.SUNKEN).pack(fill=tk.BOTH, expand=True, padx=5)
# 2. Grid布局演示
print("\n2. Grid布局管理器")
grid_frame = tk.Frame(notebook)
notebook.add(grid_frame, text="Grid布局")
tk.Label(grid_frame, text="Grid布局管理器",
font=("Arial", 14, "bold")).pack(pady=10)
tk.Label(grid_frame,
text="Grid使用行列网格来精确控制控件位置,适合复杂布局",
wraplength=400).pack(pady=5)
# Grid示例 - 计算器布局
grid_demo_frame = tk.LabelFrame(grid_frame, text="Grid示例 - 计算器布局",
padx=10, pady=10)
grid_demo_frame.pack(pady=10, padx=20)
# 显示屏
display = tk.Entry(grid_demo_frame, width=20, font=("Arial", 14),
justify=tk.RIGHT, state="readonly")
display.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky="ew")
# 按钮布局
buttons = [
['C', '±', '%', '÷'],
['7', '8', '9', '×'],
['4', '5', '6', '-'],
['1', '2', '3', '+'],
['0', '', '.', '=']
]
for i, row in enumerate(buttons, 1):
for j, text in enumerate(row):
if text: # 跳过空按钮
if text == '0':
# 0按钮占两列
btn = tk.Button(grid_demo_frame, text=text, width=5, height=2)
btn.grid(row=i, column=j, columnspan=2, padx=2, pady=2, sticky="ew")
elif text in ['÷', '×', '-', '+', '=']:
# 运算符按钮
btn = tk.Button(grid_demo_frame, text=text, width=5, height=2,
bg="orange", fg="white")
btn.grid(row=i, column=j, padx=2, pady=2, sticky="ew")
elif text in ['C', '±', '%']:
# 功能按钮
btn = tk.Button(grid_demo_frame, text=text, width=5, height=2,
bg="lightgray")
btn.grid(row=i, column=j, padx=2, pady=2, sticky="ew")
else:
# 数字按钮
btn = tk.Button(grid_demo_frame, text=text, width=5, height=2)
btn.grid(row=i, column=j, padx=2, pady=2, sticky="ew")
# 3. Place布局演示
print("\n3. Place布局管理器")
place_frame = tk.Frame(notebook)
notebook.add(place_frame, text="Place布局")
tk.Label(place_frame, text="Place布局管理器",
font=("Arial", 14, "bold")).pack(pady=10)
tk.Label(place_frame,
text="Place使用绝对或相对坐标精确定位控件,适合特殊布局需求",
wraplength=400).pack(pady=5)
# Place示例
place_demo_frame = tk.LabelFrame(place_frame, text="Place示例",
width=400, height=300)
place_demo_frame.pack(pady=10, padx=20)
place_demo_frame.pack_propagate(False) # 防止框架自动调整大小
# 绝对定位
tk.Button(place_demo_frame, text="绝对定位(10,10)", bg="lightblue").place(x=10, y=10)
tk.Button(place_demo_frame, text="绝对定位(200,50)", bg="lightgreen").place(x=200, y=50)
# 相对定位
tk.Button(place_demo_frame, text="相对定位(50%,30%)",
bg="lightcoral").place(relx=0.5, rely=0.3, anchor=tk.CENTER)
tk.Button(place_demo_frame, text="右下角",
bg="lightyellow").place(relx=1.0, rely=1.0, anchor=tk.SE, x=-10, y=-10)
# 大小控制
tk.Label(place_demo_frame, text="相对大小控制", bg="lightpink",
relief=tk.RAISED).place(relx=0.1, rely=0.6, relwidth=0.8, relheight=0.2)
# 4. 混合布局演示
print("\n4. 混合布局管理器")
mixed_frame = tk.Frame(notebook)
notebook.add(mixed_frame, text="混合布局")
tk.Label(mixed_frame, text="混合布局管理器",
font=("Arial", 14, "bold")).pack(pady=10)
tk.Label(mixed_frame,
text="在不同的容器中可以使用不同的布局管理器,实现复杂界面",
wraplength=400).pack(pady=5)
# 混合布局示例 - 文本编辑器界面
mixed_demo_frame = tk.Frame(mixed_frame)
mixed_demo_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 顶部工具栏 - 使用Pack
toolbar = tk.Frame(mixed_demo_frame, bg="lightgray", height=40)
toolbar.pack(fill=tk.X, pady=(0, 5))
toolbar.pack_propagate(False)
tk.Button(toolbar, text="新建").pack(side=tk.LEFT, padx=5, pady=5)
tk.Button(toolbar, text="打开").pack(side=tk.LEFT, padx=5, pady=5)
tk.Button(toolbar, text="保存").pack(side=tk.LEFT, padx=5, pady=5)
ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
tk.Button(toolbar, text="复制").pack(side=tk.LEFT, padx=5, pady=5)
tk.Button(toolbar, text="粘贴").pack(side=tk.LEFT, padx=5, pady=5)
# 主要内容区域 - 使用Grid
content_frame = tk.Frame(mixed_demo_frame)
content_frame.pack(fill=tk.BOTH, expand=True)
# 配置Grid权重
content_frame.grid_rowconfigure(0, weight=1)
content_frame.grid_columnconfigure(1, weight=1)
# 左侧文件树
tree_frame = tk.LabelFrame(content_frame, text="文件", width=150)
tree_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 5))
tree_frame.grid_propagate(False)
file_list = tk.Listbox(tree_frame)
file_list.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
for i in range(1, 6):
file_list.insert(tk.END, f"文件{i}.txt")
# 右侧编辑区域
edit_frame = tk.LabelFrame(content_frame, text="编辑器")
edit_frame.grid(row=0, column=1, sticky="nsew")
edit_text = tk.Text(edit_frame, wrap=tk.WORD)
edit_scrollbar = tk.Scrollbar(edit_frame)
edit_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 0), pady=5)
edit_scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=5, padx=(0, 5))
edit_text.config(yscrollcommand=edit_scrollbar.set)
edit_scrollbar.config(command=edit_text.yview)
# 底部状态栏 - 使用Pack
status_bar = tk.Frame(mixed_demo_frame, bg="lightgray", height=25)
status_bar.pack(fill=tk.X, pady=(5, 0))
status_bar.pack_propagate(False)
tk.Label(status_bar, text="就绪", bg="lightgray").pack(side=tk.LEFT, padx=5)
tk.Label(status_bar, text="行: 1, 列: 1", bg="lightgray").pack(side=tk.RIGHT, padx=5)
print("\n布局管理演示窗口已创建,请查看GUI界面")
print("关闭窗口以继续下一个演示")
# 启动事件循环
root.mainloop()
# 运行布局管理演示
layout_management_demo()
# 三、事件处理
# 3.1 事件绑定和处理
import tkinter as tk
from tkinter import messagebox
import time
def event_handling_demo():
"""事件处理演示"""
print("=== 事件处理演示 ===")
# 创建主窗口
root = tk.Tk()
root.title("事件处理演示")
root.geometry("700x600")
# 事件日志显示区域
log_frame = tk.LabelFrame(root, text="事件日志", padx=5, pady=5)
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
log_text = tk.Text(log_frame, height=15, font=("Consolas", 10))
log_scrollbar = tk.Scrollbar(log_frame)
log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
log_text.config(yscrollcommand=log_scrollbar.set)
log_scrollbar.config(command=log_text.yview)
def log_event(message):
"""记录事件到日志"""
timestamp = time.strftime("%H:%M:%S")
log_text.insert(tk.END, f"[{timestamp}] {message}\n")
log_text.see(tk.END)
print(f" 事件: {message}")
def clear_log():
"""清空日志"""
log_text.delete("1.0", tk.END)
log_event("日志已清空")
# 1. 鼠标事件
print("\n1. 鼠标事件处理")
mouse_frame = tk.LabelFrame(root, text="鼠标事件测试区域", padx=10, pady=10)
mouse_frame.pack(fill=tk.X, padx=10, pady=5)
mouse_label = tk.Label(mouse_frame, text="鼠标事件测试区域",
bg="lightblue", width=30, height=3,
relief=tk.RAISED, cursor="hand2")
mouse_label.pack(pady=5)
# 鼠标事件处理函数
def on_mouse_click(event):
log_event(f"鼠标点击: 按钮{event.num}, 坐标({event.x}, {event.y})")
def on_mouse_double_click(event):
log_event(f"鼠标双击: 坐标({event.x}, {event.y})")
mouse_label.config(bg="lightgreen")
root.after(500, lambda: mouse_label.config(bg="lightblue"))
def on_mouse_enter(event):
log_event("鼠标进入区域")
mouse_label.config(relief=tk.SUNKEN)
def on_mouse_leave(event):
log_event("鼠标离开区域")
mouse_label.config(relief=tk.RAISED)
def on_mouse_motion(event):
# 只记录每10个像素的移动,避免日志过多
if event.x % 10 == 0 and event.y % 10 == 0:
log_event(f"鼠标移动: 坐标({event.x}, {event.y})")
# 绑定鼠标事件
mouse_label.bind("<Button-1>", on_mouse_click) # 左键点击
mouse_label.bind("<Button-3>", on_mouse_click) # 右键点击
mouse_label.bind("<Double-Button-1>", on_mouse_double_click) # 双击
mouse_label.bind("<Enter>", on_mouse_enter) # 鼠标进入
mouse_label.bind("<Leave>", on_mouse_leave) # 鼠标离开
mouse_label.bind("<Motion>", on_mouse_motion) # 鼠标移动
# 2. 键盘事件
print("\n2. 键盘事件处理")
keyboard_frame = tk.LabelFrame(root, text="键盘事件测试", padx=10, pady=10)
keyboard_frame.pack(fill=tk.X, padx=10, pady=5)
tk.Label(keyboard_frame, text="点击下面的输入框并输入内容:").pack()
keyboard_entry = tk.Entry(keyboard_frame, font=("Arial", 12), width=40)
keyboard_entry.pack(pady=5)
keyboard_entry.focus_set() # 设置焦点
# 键盘事件处理函数
def on_key_press(event):
key_info = f"按键按下: '{event.char}' (键码: {event.keycode})"
if event.char.isprintable():
log_event(key_info)
elif event.keysym in ['Return', 'BackSpace', 'Delete', 'Tab']:
log_event(f"特殊键按下: {event.keysym}")
def on_key_release(event):
if event.keysym == 'Return':
content = keyboard_entry.get()
log_event(f"回车键释放,当前内容: '{content}'")
def on_focus_in(event):
log_event("输入框获得焦点")
keyboard_entry.config(bg="lightyellow")
def on_focus_out(event):
log_event("输入框失去焦点")
keyboard_entry.config(bg="white")
# 绑定键盘事件
keyboard_entry.bind("<KeyPress>", on_key_press)
keyboard_entry.bind("<KeyRelease>", on_key_release)
keyboard_entry.bind("<FocusIn>", on_focus_in)
keyboard_entry.bind("<FocusOut>", on_focus_out)
# 3. 窗口事件
print("\n3. 窗口事件处理")
def on_window_resize(event):
if event.widget == root: # 只处理主窗口的调整事件
log_event(f"窗口大小调整: {event.width}x{event.height}")
def on_window_focus_in(event):
if event.widget == root:
log_event("窗口获得焦点")
def on_window_focus_out(event):
if event.widget == root:
log_event("窗口失去焦点")
# 绑定窗口事件
root.bind("<Configure>", on_window_resize)
root.bind("<FocusIn>", on_window_focus_in)
root.bind("<FocusOut>", on_window_focus_out)
# 4. 自定义事件
print("\n4. 自定义事件处理")
custom_frame = tk.LabelFrame(root, text="自定义事件", padx=10, pady=10)
custom_frame.pack(fill=tk.X, padx=10, pady=5)
# 自定义事件处理函数
def trigger_custom_event():
# 生成自定义事件
root.event_generate("<<CustomEvent>>", when="tail")
log_event("触发自定义事件")
def on_custom_event(event):
log_event("处理自定义事件")
messagebox.showinfo("自定义事件", "自定义事件被触发了!")
# 绑定自定义事件
root.bind("<<CustomEvent>>", on_custom_event)
tk.Button(custom_frame, text="触发自定义事件",
command=trigger_custom_event).pack(side=tk.LEFT, padx=5)
# 5. 定时器事件
print("\n5. 定时器事件处理")
timer_running = False
timer_count = 0
def timer_callback():
global timer_count
if timer_running:
timer_count += 1
log_event(f"定时器事件: 第{timer_count}次")
# 每秒触发一次
root.after(1000, timer_callback)
def start_timer():
global timer_running, timer_count
if not timer_running:
timer_running = True
timer_count = 0
log_event("启动定时器")
timer_callback()
def stop_timer():
global timer_running
if timer_running:
timer_running = False
log_event("停止定时器")
tk.Button(custom_frame, text="启动定时器",
command=start_timer).pack(side=tk.LEFT, padx=5)
tk.Button(custom_frame, text="停止定时器",
command=stop_timer).pack(side=tk.LEFT, padx=5)
# 控制按钮
control_frame = tk.Frame(root)
control_frame.pack(fill=tk.X, padx=10, pady=5)
tk.Button(control_frame, text="清空日志",
command=clear_log).pack(side=tk.LEFT, padx=5)
def show_help():
help_text = """事件处理演示说明:
1. 鼠标事件: 在蓝色区域进行鼠标操作
2. 键盘事件: 在输入框中输入内容
3. 窗口事件: 调整窗口大小或切换焦点
4. 自定义事件: 点击按钮触发
5. 定时器事件: 启动/停止定时器
所有事件都会记录在日志中。"""
messagebox.showinfo("帮助", help_text)
tk.Button(control_frame, text="帮助",
command=show_help).pack(side=tk.RIGHT, padx=5)
# 初始化日志
log_event("事件处理演示程序启动")
print("\n事件处理演示窗口已创建,请查看GUI界面")
print("尝试各种操作来触发不同的事件")
print("关闭窗口以继续下一个演示")
# 窗口关闭事件
def on_closing():
stop_timer() # 停止定时器
log_event("程序即将退出")
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
# 启动事件循环
root.mainloop()
# 运行事件处理演示
event_handling_demo()
# 四、高级控件
# 4.1 菜单和对话框
import tkinter as tk
from tkinter import ttk, messagebox, filedialog, colorchooser, simpledialog
import os
def advanced_widgets_demo():
"""高级控件演示"""
print("=== 高级控件演示 ===")
# 创建主窗口
root = tk.Tk()
root.title("高级控件演示")
root.geometry("800x600")
# 状态变量
current_file = None
# 1. 菜单栏
print("\n1. 创建菜单栏")
menubar = tk.Menu(root)
root.config(menu=menubar)
# 文件菜单
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="文件", menu=file_menu)
def new_file():
global current_file
if messagebox.askyesno("新建文件", "确定要新建文件吗?未保存的内容将丢失。"):
text_area.delete("1.0", tk.END)
current_file = None
root.title("高级控件演示 - 新文件")
status_var.set("新建文件")
def open_file():
global current_file
filename = filedialog.askopenfilename(
title="打开文件",
filetypes=[("文本文件", "*.txt"), ("Python文件", "*.py"), ("所有文件", "*.*")]
)
if filename:
try:
with open(filename, 'r', encoding='utf-8') as file:
content = file.read()
text_area.delete("1.0", tk.END)
text_area.insert("1.0", content)
current_file = filename
root.title(f"高级控件演示 - {os.path.basename(filename)}")
status_var.set(f"已打开: {filename}")
except Exception as e:
messagebox.showerror("错误", f"无法打开文件: {e}")
def save_file():
global current_file
if current_file:
try:
content = text_area.get("1.0", tk.END)
with open(current_file, 'w', encoding='utf-8') as file:
file.write(content)
status_var.set(f"已保存: {current_file}")
except Exception as e:
messagebox.showerror("错误", f"无法保存文件: {e}")
else:
save_as_file()
def save_as_file():
global current_file
filename = filedialog.asksaveasfilename(
title="另存为",
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("Python文件", "*.py"), ("所有文件", "*.*")]
)
if filename:
try:
content = text_area.get("1.0", tk.END)
with open(filename, 'w', encoding='utf-8') as file:
file.write(content)
current_file = filename
root.title(f"高级控件演示 - {os.path.basename(filename)}")
status_var.set(f"已保存为: {filename}")
except Exception as e:
messagebox.showerror("错误", f"无法保存文件: {e}")
def exit_app():
if messagebox.askyesno("退出", "确定要退出程序吗?"):
root.destroy()
file_menu.add_command(label="新建", command=new_file, accelerator="Ctrl+N")
file_menu.add_command(label="打开", command=open_file, accelerator="Ctrl+O")
file_menu.add_separator()
file_menu.add_command(label="保存", command=save_file, accelerator="Ctrl+S")
file_menu.add_command(label="另存为", command=save_as_file, accelerator="Ctrl+Shift+S")
file_menu.add_separator()
file_menu.add_command(label="退出", command=exit_app, accelerator="Ctrl+Q")
# 编辑菜单
edit_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="编辑", menu=edit_menu)
def undo():
try:
text_area.edit_undo()
except tk.TclError:
pass
def redo():
try:
text_area.edit_redo()
except tk.TclError:
pass
def cut():
try:
text_area.event_generate("<<Cut>>")
except tk.TclError:
pass
def copy():
try:
text_area.event_generate("<<Copy>>")
except tk.TclError:
pass
def paste():
try:
text_area.event_generate("<<Paste>>")
except tk.TclError:
pass
def select_all():
text_area.tag_add(tk.SEL, "1.0", tk.END)
text_area.mark_set(tk.INSERT, "1.0")
text_area.see(tk.INSERT)
edit_menu.add_command(label="撤销", command=undo, accelerator="Ctrl+Z")
edit_menu.add_command(label="重做", command=redo, accelerator="Ctrl+Y")
edit_menu.add_separator()
edit_menu.add_command(label="剪切", command=cut, accelerator="Ctrl+X")
edit_menu.add_command(label="复制", command=copy, accelerator="Ctrl+C")
edit_menu.add_command(label="粘贴", command=paste, accelerator="Ctrl+V")
edit_menu.add_separator()
edit_menu.add_command(label="全选", command=select_all, accelerator="Ctrl+A")
# 格式菜单
format_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="格式", menu=format_menu)
def change_font():
# 简单的字体选择对话框
font_families = ['Arial', 'Times New Roman', 'Courier New', '宋体', '微软雅黑']
font_sizes = ['8', '10', '12', '14', '16', '18', '20', '24']
font_window = tk.Toplevel(root)
font_window.title("字体设置")
font_window.geometry("400x300")
font_window.transient(root)
font_window.grab_set()
tk.Label(font_window, text="字体:").grid(row=0, column=0, sticky="w", padx=10, pady=5)
font_var = tk.StringVar(value="Arial")
font_combo = ttk.Combobox(font_window, textvariable=font_var, values=font_families)
font_combo.grid(row=0, column=1, padx=10, pady=5)
tk.Label(font_window, text="大小:").grid(row=1, column=0, sticky="w", padx=10, pady=5)
size_var = tk.StringVar(value="12")
size_combo = ttk.Combobox(font_window, textvariable=size_var, values=font_sizes)
size_combo.grid(row=1, column=1, padx=10, pady=5)
def apply_font():
font_family = font_var.get()
font_size = int(size_var.get())
text_area.config(font=(font_family, font_size))
font_window.destroy()
tk.Button(font_window, text="确定", command=apply_font).grid(row=2, column=0, columnspan=2, pady=20)
def change_color():
color = colorchooser.askcolor(title="选择文字颜色")
if color[1]: # 如果选择了颜色
text_area.config(fg=color[1])
def change_bg_color():
color = colorchooser.askcolor(title="选择背景颜色")
if color[1]: # 如果选择了颜色
text_area.config(bg=color[1])
format_menu.add_command(label="字体", command=change_font)
format_menu.add_command(label="文字颜色", command=change_color)
format_menu.add_command(label="背景颜色", command=change_bg_color)
# 帮助菜单
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="帮助", menu=help_menu)
def show_about():
messagebox.showinfo("关于", "高级控件演示程序\n\n版本: 1.0\n作者: Python学习者")
help_menu.add_command(label="关于", command=show_about)
# 2. 工具栏
print("\n2. 创建工具栏")
toolbar = tk.Frame(root, bg="lightgray", height=40)
toolbar.pack(fill=tk.X)
toolbar.pack_propagate(False)
# 工具栏按钮
tk.Button(toolbar, text="新建", command=new_file, relief=tk.FLAT).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(toolbar, text="打开", command=open_file, relief=tk.FLAT).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(toolbar, text="保存", command=save_file, relief=tk.FLAT).pack(side=tk.LEFT, padx=2, pady=2)
ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
tk.Button(toolbar, text="剪切", command=cut, relief=tk.FLAT).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(toolbar, text="复制", command=copy, relief=tk.FLAT).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(toolbar, text="粘贴", command=paste, relief=tk.FLAT).pack(side=tk.LEFT, padx=2, pady=2)
# 3. 主要内容区域
print("\n3. 创建主要内容区域")
# 创建文本编辑区域
text_frame = tk.Frame(root)
text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
text_area = tk.Text(text_frame, wrap=tk.WORD, undo=True, font=("Consolas", 12))
text_scrollbar_y = tk.Scrollbar(text_frame, orient=tk.VERTICAL)
text_scrollbar_x = tk.Scrollbar(text_frame, orient=tk.HORIZONTAL)
text_area.grid(row=0, column=0, sticky="nsew")
text_scrollbar_y.grid(row=0, column=1, sticky="ns")
text_scrollbar_x.grid(row=1, column=0, sticky="ew")
text_frame.grid_rowconfigure(0, weight=1)
text_frame.grid_columnconfigure(0, weight=1)
text_area.config(yscrollcommand=text_scrollbar_y.set, xscrollcommand=text_scrollbar_x.set)
text_scrollbar_y.config(command=text_area.yview)
text_scrollbar_x.config(command=text_area.xview)
# 插入示例文本
sample_text = """欢迎使用高级控件演示程序!
这是一个功能完整的文本编辑器示例,包含以下功能:
1. 完整的菜单栏
- 文件操作:新建、打开、保存、另存为
- 编辑操作:撤销、重做、剪切、复制、粘贴、全选
- 格式设置:字体、颜色
- 帮助信息
2. 工具栏快捷按钮
3. 文本编辑区域
- 支持撤销/重做
- 支持滚动条
- 支持自动换行
4. 状态栏显示
试试使用菜单和工具栏的各种功能吧!"""
text_area.insert("1.0", sample_text)
# 4. 状态栏
print("\n4. 创建状态栏")
status_frame = tk.Frame(root, bg="lightgray", height=25)
status_frame.pack(fill=tk.X)
status_frame.pack_propagate(False)
status_var = tk.StringVar(value="就绪")
status_label = tk.Label(status_frame, textvariable=status_var, bg="lightgray")
status_label.pack(side=tk.LEFT, padx=5)
# 显示光标位置
cursor_var = tk.StringVar(value="行: 1, 列: 1")
cursor_label = tk.Label(status_frame, textvariable=cursor_var, bg="lightgray")
cursor_label.pack(side=tk.RIGHT, padx=5)
def update_cursor_position(event=None):
cursor_pos = text_area.index(tk.INSERT)
line, col = cursor_pos.split('.')
cursor_var.set(f"行: {line}, 列: {int(col)+1}")
text_area.bind("<KeyRelease>", update_cursor_position)
text_area.bind("<ButtonRelease>", update_cursor_position)
# 5. 键盘快捷键
print("\n5. 绑定键盘快捷键")
root.bind("<Control-n>", lambda e: new_file())
root.bind("<Control-o>", lambda e: open_file())
root.bind("<Control-s>", lambda e: save_file())
root.bind("<Control-Shift-S>", lambda e: save_as_file())
root.bind("<Control-q>", lambda e: exit_app())
root.bind("<Control-z>", lambda e: undo())
root.bind("<Control-y>", lambda e: redo())
root.bind("<Control-a>", lambda e: select_all())
# 6. 右键菜单
print("\n6. 创建右键菜单")
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="剪切", command=cut)
context_menu.add_command(label="复制", command=copy)
context_menu.add_command(label="粘贴", command=paste)
context_menu.add_separator()
context_menu.add_command(label="全选", command=select_all)
def show_context_menu(event):
try:
context_menu.tk_popup(event.x_root, event.y_root)
finally:
context_menu.grab_release()
text_area.bind("<Button-3>", show_context_menu) # 右键点击
print("\n高级控件演示窗口已创建,请查看GUI界面")
print("尝试使用菜单、工具栏和各种功能")
print("关闭窗口以继续下一个演示")
# 启动事件循环
root.mainloop()
# 运行高级控件演示
advanced_widgets_demo()
# 4.2 列表和树形控件
import tkinter as tk
from tkinter import ttk, messagebox
import random
import datetime
def list_tree_demo():
"""列表和树形控件演示"""
print("=== 列表和树形控件演示 ===")
# 创建主窗口
root = tk.Tk()
root.title("列表和树形控件演示")
root.geometry("900x700")
# 创建笔记本控件
notebook = ttk.Notebook(root)
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 1. 列表框演示
print("\n1. 列表框控件演示")
listbox_frame = tk.Frame(notebook)
notebook.add(listbox_frame, text="列表框")
tk.Label(listbox_frame, text="列表框控件演示",
font=("Arial", 14, "bold")).pack(pady=10)
# 创建列表框区域
list_container = tk.Frame(listbox_frame)
list_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 左侧 - 单选列表框
left_frame = tk.LabelFrame(list_container, text="单选列表框", padx=10, pady=10)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))
single_listbox = tk.Listbox(left_frame, selectmode=tk.SINGLE, height=10)
single_scrollbar = tk.Scrollbar(left_frame, orient=tk.VERTICAL)
single_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
single_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
single_listbox.config(yscrollcommand=single_scrollbar.set)
single_scrollbar.config(command=single_listbox.yview)
# 添加示例数据
cities = ["北京", "上海", "广州", "深圳", "杭州", "南京", "武汉", "成都", "西安", "重庆"]
for city in cities:
single_listbox.insert(tk.END, city)
def on_single_select(event):
selection = single_listbox.curselection()
if selection:
selected_city = single_listbox.get(selection[0])
messagebox.showinfo("选择", f"您选择了: {selected_city}")
single_listbox.bind("<<ListboxSelect>>", on_single_select)
# 右侧 - 多选列表框
right_frame = tk.LabelFrame(list_container, text="多选列表框", padx=10, pady=10)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(10, 0))
multi_listbox = tk.Listbox(right_frame, selectmode=tk.MULTIPLE, height=10)
multi_scrollbar = tk.Scrollbar(right_frame, orient=tk.VERTICAL)
multi_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
multi_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
multi_listbox.config(yscrollcommand=multi_scrollbar.set)
multi_scrollbar.config(command=multi_listbox.yview)
# 添加示例数据
languages = ["Python", "Java", "C++", "JavaScript", "Go", "Rust", "Swift", "Kotlin", "C#", "PHP"]
for lang in languages:
multi_listbox.insert(tk.END, lang)
def show_multi_selection():
selections = multi_listbox.curselection()
if selections:
selected_langs = [multi_listbox.get(i) for i in selections]
messagebox.showinfo("多选结果", f"您选择了: {', '.join(selected_langs)}")
else:
messagebox.showinfo("多选结果", "没有选择任何项目")
tk.Button(right_frame, text="查看选择", command=show_multi_selection).pack(pady=5)
# 2. 树形控件演示
print("\n2. 树形控件演示")
tree_frame = tk.Frame(notebook)
notebook.add(tree_frame, text="树形控件")
tk.Label(tree_frame, text="树形控件演示",
font=("Arial", 14, "bold")).pack(pady=10)
# 创建树形控件
tree_container = tk.Frame(tree_frame)
tree_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 定义列
columns = ("name", "type", "size", "modified")
tree = ttk.Treeview(tree_container, columns=columns, show="tree headings", height=15)
# 配置列
tree.heading("#0", text="文件/文件夹", anchor=tk.W)
tree.heading("name", text="名称", anchor=tk.W)
tree.heading("type", text="类型", anchor=tk.W)
tree.heading("size", text="大小", anchor=tk.E)
tree.heading("modified", text="修改时间", anchor=tk.W)
tree.column("#0", width=200, minwidth=100)
tree.column("name", width=150, minwidth=100)
tree.column("type", width=100, minwidth=80)
tree.column("size", width=100, minwidth=80)
tree.column("modified", width=150, minwidth=120)
# 添加滚动条
tree_scrollbar_y = ttk.Scrollbar(tree_container, orient=tk.VERTICAL, command=tree.yview)
tree_scrollbar_x = ttk.Scrollbar(tree_container, orient=tk.HORIZONTAL, command=tree.xview)
tree.configure(yscrollcommand=tree_scrollbar_y.set, xscrollcommand=tree_scrollbar_x.set)
tree.grid(row=0, column=0, sticky="nsew")
tree_scrollbar_y.grid(row=0, column=1, sticky="ns")
tree_scrollbar_x.grid(row=1, column=0, sticky="ew")
tree_container.grid_rowconfigure(0, weight=1)
tree_container.grid_columnconfigure(0, weight=1)
# 添加示例数据
def add_sample_data():
# 根目录
root_id = tree.insert("", "end", text="我的电脑", values=("", "文件夹", "", ""))
# C盘
c_drive = tree.insert(root_id, "end", text="C:\\", values=("C:\\", "磁盘", "500 GB", "2023-01-01"))
# 程序文件夹
program_files = tree.insert(c_drive, "end", text="Program Files",
values=("Program Files", "文件夹", "15 GB", "2023-06-01"))
# Python文件夹
python_folder = tree.insert(program_files, "end", text="Python",
values=("Python", "文件夹", "2 GB", "2023-06-15"))
# Python文件
tree.insert(python_folder, "end", text="python.exe",
values=("python.exe", "应用程序", "25 MB", "2023-06-15"))
tree.insert(python_folder, "end", text="pip.exe",
values=("pip.exe", "应用程序", "5 MB", "2023-06-15"))
# 用户文件夹
users = tree.insert(c_drive, "end", text="Users",
values=("Users", "文件夹", "50 GB", "2023-05-01"))
user_folder = tree.insert(users, "end", text="用户",
values=("用户", "文件夹", "30 GB", "2023-06-01"))
# 文档文件夹
documents = tree.insert(user_folder, "end", text="Documents",
values=("Documents", "文件夹", "10 GB", "2023-06-10"))
# 示例文档
tree.insert(documents, "end", text="report.docx",
values=("report.docx", "Word文档", "2 MB", "2023-06-15"))
tree.insert(documents, "end", text="data.xlsx",
values=("data.xlsx", "Excel文档", "5 MB", "2023-06-14"))
tree.insert(documents, "end", text="presentation.pptx",
values=("presentation.pptx", "PowerPoint", "10 MB", "2023-06-13"))
# D盘
d_drive = tree.insert(root_id, "end", text="D:\\", values=("D:\\", "磁盘", "1 TB", "2023-01-01"))
# 项目文件夹
projects = tree.insert(d_drive, "end", text="Projects",
values=("Projects", "文件夹", "20 GB", "2023-06-01"))
python_project = tree.insert(projects, "end", text="PythonProject",
values=("PythonProject", "文件夹", "500 MB", "2023-06-15"))
tree.insert(python_project, "end", text="main.py",
values=("main.py", "Python文件", "10 KB", "2023-06-15"))
tree.insert(python_project, "end", text="requirements.txt",
values=("requirements.txt", "文本文件", "1 KB", "2023-06-10"))
# 展开根节点
tree.item(root_id, open=True)
tree.item(c_drive, open=True)
add_sample_data()
# 树形控件事件处理
def on_tree_select(event):
selection = tree.selection()
if selection:
item = selection[0]
item_text = tree.item(item, "text")
item_values = tree.item(item, "values")
print(f" 选择了: {item_text}, 值: {item_values}")
def on_tree_double_click(event):
selection = tree.selection()
if selection:
item = selection[0]
item_text = tree.item(item, "text")
messagebox.showinfo("双击", f"双击了: {item_text}")
tree.bind("<<TreeviewSelect>>", on_tree_select)
tree.bind("<Double-1>", on_tree_double_click)
# 树形控件操作按钮
tree_buttons = tk.Frame(tree_frame)
tree_buttons.pack(fill=tk.X, padx=20, pady=5)
def expand_all():
def expand_item(item):
tree.item(item, open=True)
for child in tree.get_children(item):
expand_item(child)
for item in tree.get_children():
expand_item(item)
def collapse_all():
def collapse_item(item):
tree.item(item, open=False)
for child in tree.get_children(item):
collapse_item(child)
for item in tree.get_children():
collapse_item(item)
def add_item():
selection = tree.selection()
if selection:
parent = selection[0]
new_item = f"新项目_{random.randint(1, 1000)}"
tree.insert(parent, "end", text=new_item,
values=(new_item, "文件", "1 KB", datetime.datetime.now().strftime("%Y-%m-%d")))
else:
messagebox.showwarning("警告", "请先选择一个父节点")
def delete_item():
selection = tree.selection()
if selection:
if messagebox.askyesno("确认", "确定要删除选中的项目吗?"):
tree.delete(selection[0])
else:
messagebox.showwarning("警告", "请先选择要删除的项目")
tk.Button(tree_buttons, text="展开全部", command=expand_all).pack(side=tk.LEFT, padx=5)
tk.Button(tree_buttons, text="折叠全部", command=collapse_all).pack(side=tk.LEFT, padx=5)
tk.Button(tree_buttons, text="添加项目", command=add_item).pack(side=tk.LEFT, padx=5)
tk.Button(tree_buttons, text="删除项目", command=delete_item).pack(side=tk.LEFT, padx=5)
# 3. 组合框演示
print("\n3. 组合框控件演示")
combo_frame = tk.Frame(notebook)
notebook.add(combo_frame, text="组合框")
tk.Label(combo_frame, text="组合框控件演示",
font=("Arial", 14, "bold")).pack(pady=10)
combo_container = tk.Frame(combo_frame)
combo_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 只读组合框
readonly_frame = tk.LabelFrame(combo_container, text="只读组合框", padx=10, pady=10)
readonly_frame.pack(fill=tk.X, pady=5)
tk.Label(readonly_frame, text="选择编程语言:").pack(anchor=tk.W)
readonly_var = tk.StringVar()
readonly_combo = ttk.Combobox(readonly_frame, textvariable=readonly_var,
values=languages, state="readonly", width=30)
readonly_combo.pack(pady=5, anchor=tk.W)
readonly_combo.set("Python") # 设置默认值
def on_readonly_select(event):
selected = readonly_var.get()
print(f" 选择了编程语言: {selected}")
readonly_combo.bind("<<ComboboxSelected>>", on_readonly_select)
# 可编辑组合框
editable_frame = tk.LabelFrame(combo_container, text="可编辑组合框", padx=10, pady=10)
editable_frame.pack(fill=tk.X, pady=5)
tk.Label(editable_frame, text="输入或选择城市:").pack(anchor=tk.W)
editable_var = tk.StringVar()
editable_combo = ttk.Combobox(editable_frame, textvariable=editable_var,
values=cities, width=30)
editable_combo.pack(pady=5, anchor=tk.W)
def on_editable_change(event):
current = editable_var.get()
print(f" 当前输入: {current}")
editable_combo.bind("<KeyRelease>", on_editable_change)
editable_combo.bind("<<ComboboxSelected>>", on_editable_change)
# 动态组合框
dynamic_frame = tk.LabelFrame(combo_container, text="动态组合框", padx=10, pady=10)
dynamic_frame.pack(fill=tk.X, pady=5)
tk.Label(dynamic_frame, text="类别:").pack(anchor=tk.W)
category_var = tk.StringVar()
category_combo = ttk.Combobox(dynamic_frame, textvariable=category_var,
values=["水果", "蔬菜", "肉类"], state="readonly", width=30)
category_combo.pack(pady=5, anchor=tk.W)
tk.Label(dynamic_frame, text="具体项目:").pack(anchor=tk.W)
item_var = tk.StringVar()
item_combo = ttk.Combobox(dynamic_frame, textvariable=item_var,
state="readonly", width=30)
item_combo.pack(pady=5, anchor=tk.W)
# 动态更新选项
category_items = {
"水果": ["苹果", "香蕉", "橙子", "葡萄", "草莓"],
"蔬菜": ["白菜", "萝卜", "土豆", "西红柿", "黄瓜"],
"肉类": ["猪肉", "牛肉", "鸡肉", "鱼肉", "羊肉"]
}
def update_items(event):
category = category_var.get()
if category in category_items:
item_combo['values'] = category_items[category]
item_combo.set("") # 清空当前选择
category_combo.bind("<<ComboboxSelected>>", update_items)
# 显示选择结果
result_frame = tk.Frame(combo_container)
result_frame.pack(fill=tk.X, pady=10)
def show_selections():
results = []
if readonly_var.get():
results.append(f"编程语言: {readonly_var.get()}")
if editable_var.get():
results.append(f"城市: {editable_var.get()}")
if category_var.get() and item_var.get():
results.append(f"食物: {category_var.get()} - {item_var.get()}")
if results:
messagebox.showinfo("选择结果", "\n".join(results))
else:
messagebox.showinfo("选择结果", "没有任何选择")
tk.Button(result_frame, text="显示所有选择", command=show_selections).pack()
print("\n列表和树形控件演示窗口已创建,请查看GUI界面")
print("关闭窗口以继续下一个演示")
# 启动事件循环
root.mainloop()
# 运行列表和树形控件演示
list_tree_demo()
# 4.3 画布控件
import tkinter as tk
from tkinter import ttk, colorchooser
import math
import random
def canvas_demo():
"""画布控件演示"""
print("=== 画布控件演示 ===")
# 创建主窗口
root = tk.Tk()
root.title("画布控件演示")
root.geometry("1000x700")
# 创建笔记本控件
notebook = ttk.Notebook(root)
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 1. 基础绘图
print("\n1. 基础绘图演示")
basic_frame = tk.Frame(notebook)
notebook.add(basic_frame, text="基础绘图")
tk.Label(basic_frame, text="基础绘图演示",
font=("Arial", 14, "bold")).pack(pady=10)
# 创建画布
basic_canvas = tk.Canvas(basic_frame, width=600, height=400, bg="white", relief=tk.SUNKEN, bd=2)
basic_canvas.pack(pady=10)
# 绘制基本图形
def draw_basic_shapes():
basic_canvas.delete("all") # 清空画布
# 绘制线条
basic_canvas.create_line(50, 50, 200, 50, fill="red", width=3)
basic_canvas.create_line(50, 70, 200, 120, fill="blue", width=2)
# 绘制矩形
basic_canvas.create_rectangle(250, 50, 350, 120, fill="lightblue", outline="blue", width=2)
basic_canvas.create_rectangle(370, 50, 470, 120, fill="", outline="green", width=3)
# 绘制椭圆
basic_canvas.create_oval(50, 150, 150, 220, fill="yellow", outline="orange", width=2)
basic_canvas.create_oval(170, 150, 220, 200, fill="pink", outline="red")
# 绘制多边形
points = [250, 150, 300, 180, 350, 150, 370, 200, 280, 220, 230, 200]
basic_canvas.create_polygon(points, fill="lightgreen", outline="darkgreen", width=2)
# 绘制弧形
basic_canvas.create_arc(400, 150, 500, 220, start=0, extent=180, fill="lightcoral", outline="red", width=2)
basic_canvas.create_arc(520, 150, 580, 220, start=45, extent=90, fill="", outline="purple", width=3, style=tk.ARC)
# 绘制文本
basic_canvas.create_text(300, 250, text="Canvas绘图演示", font=("Arial", 16, "bold"), fill="blue")
basic_canvas.create_text(300, 280, text="支持多种图形和文本", font=("宋体", 12), fill="darkgreen")
# 绘制图像(如果有的话)
# photo = tk.PhotoImage(file="image.png")
# basic_canvas.create_image(300, 350, image=photo)
# 绘图控制按钮
basic_buttons = tk.Frame(basic_frame)
basic_buttons.pack(pady=5)
tk.Button(basic_buttons, text="绘制图形", command=draw_basic_shapes).pack(side=tk.LEFT, padx=5)
tk.Button(basic_buttons, text="清空画布", command=lambda: basic_canvas.delete("all")).pack(side=tk.LEFT, padx=5)
# 2. 交互式绘图
print("\n2. 交互式绘图演示")
interactive_frame = tk.Frame(notebook)
notebook.add(interactive_frame, text="交互绘图")
tk.Label(interactive_frame, text="交互式绘图演示",
font=("Arial", 14, "bold")).pack(pady=10)
# 工具栏
toolbar = tk.Frame(interactive_frame)
toolbar.pack(fill=tk.X, padx=10, pady=5)
# 绘图模式
draw_mode = tk.StringVar(value="line")
tk.Label(toolbar, text="绘图工具:").pack(side=tk.LEFT, padx=5)
modes = [("线条", "line"), ("矩形", "rectangle"), ("椭圆", "oval"), ("自由绘制", "free")]
for text, mode in modes:
tk.Radiobutton(toolbar, text=text, variable=draw_mode, value=mode).pack(side=tk.LEFT, padx=2)
# 颜色选择
current_color = tk.StringVar(value="black")
def choose_color():
color = colorchooser.askcolor(title="选择颜色")
if color[1]:
current_color.set(color[1])
color_label.config(bg=color[1])
tk.Label(toolbar, text="颜色:").pack(side=tk.LEFT, padx=(20, 5))
color_label = tk.Label(toolbar, bg="black", width=3, relief=tk.RAISED, cursor="hand2")
color_label.pack(side=tk.LEFT, padx=2)
color_label.bind("<Button-1>", lambda e: choose_color())
# 线条粗细
tk.Label(toolbar, text="粗细:").pack(side=tk.LEFT, padx=(20, 5))
line_width = tk.Scale(toolbar, from_=1, to=10, orient=tk.HORIZONTAL, length=100)
line_width.set(2)
line_width.pack(side=tk.LEFT, padx=2)
# 创建交互画布
interactive_canvas = tk.Canvas(interactive_frame, width=700, height=450, bg="white", relief=tk.SUNKEN, bd=2)
interactive_canvas.pack(pady=10)
# 绘图状态变量
drawing = False
start_x = start_y = 0
current_item = None
def start_draw(event):
global drawing, start_x, start_y, current_item
drawing = True
start_x, start_y = event.x, event.y
mode = draw_mode.get()
color = current_color.get()
width = line_width.get()
if mode == "free":
# 自由绘制模式,立即开始绘制
current_item = interactive_canvas.create_line(start_x, start_y, start_x, start_y,
fill=color, width=width, capstyle=tk.ROUND)
def draw(event):
global current_item
if not drawing:
return
mode = draw_mode.get()
color = current_color.get()
width = line_width.get()
if mode == "line":
# 删除之前的预览线条
if current_item:
interactive_canvas.delete(current_item)
# 绘制新的预览线条
current_item = interactive_canvas.create_line(start_x, start_y, event.x, event.y,
fill=color, width=width)
elif mode == "rectangle":
if current_item:
interactive_canvas.delete(current_item)
current_item = interactive_canvas.create_rectangle(start_x, start_y, event.x, event.y,
outline=color, width=width)
elif mode == "oval":
if current_item:
interactive_canvas.delete(current_item)
current_item = interactive_canvas.create_oval(start_x, start_y, event.x, event.y,
outline=color, width=width)
elif mode == "free":
# 自由绘制模式,扩展当前线条
coords = interactive_canvas.coords(current_item)
coords.extend([event.x, event.y])
interactive_canvas.coords(current_item, *coords)
def end_draw(event):
global drawing, current_item
drawing = False
current_item = None
# 绑定鼠标事件
interactive_canvas.bind("<Button-1>", start_draw)
interactive_canvas.bind("<B1-Motion>", draw)
interactive_canvas.bind("<ButtonRelease-1>", end_draw)
# 交互控制按钮
interactive_buttons = tk.Frame(interactive_frame)
interactive_buttons.pack(pady=5)
def clear_canvas():
interactive_canvas.delete("all")
def save_canvas():
# 这里可以实现保存功能
print(" 保存画布内容(需要额外的库支持)")
tk.Button(interactive_buttons, text="清空画布", command=clear_canvas).pack(side=tk.LEFT, padx=5)
tk.Button(interactive_buttons, text="保存画布", command=save_canvas).pack(side=tk.LEFT, padx=5)
# 3. 动画演示
print("\n3. 动画演示")
animation_frame = tk.Frame(notebook)
notebook.add(animation_frame, text="动画演示")
tk.Label(animation_frame, text="动画演示",
font=("Arial", 14, "bold")).pack(pady=10)
# 创建动画画布
anim_canvas = tk.Canvas(animation_frame, width=600, height=400, bg="black", relief=tk.SUNKEN, bd=2)
anim_canvas.pack(pady=10)
# 动画对象
class AnimatedBall:
def __init__(self, canvas, x, y, dx, dy, radius, color):
self.canvas = canvas
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.radius = radius
self.color = color
self.item = canvas.create_oval(x-radius, y-radius, x+radius, y+radius,
fill=color, outline="white")
def move(self):
# 更新位置
self.x += self.dx
self.y += self.dy
# 边界检测
canvas_width = int(self.canvas.cget("width"))
canvas_height = int(self.canvas.cget("height"))
if self.x - self.radius <= 0 or self.x + self.radius >= canvas_width:
self.dx = -self.dx
if self.y - self.radius <= 0 or self.y + self.radius >= canvas_height:
self.dy = -self.dy
# 更新画布上的位置
self.canvas.coords(self.item,
self.x - self.radius, self.y - self.radius,
self.x + self.radius, self.y + self.radius)
# 创建多个动画球
balls = []
colors = ["red", "blue", "green", "yellow", "orange", "purple", "cyan", "magenta"]
def create_balls():
anim_canvas.delete("all")
balls.clear()
for i in range(8):
x = random.randint(50, 550)
y = random.randint(50, 350)
dx = random.randint(-5, 5)
dy = random.randint(-5, 5)
if dx == 0: dx = 1
if dy == 0: dy = 1
radius = random.randint(10, 25)
color = colors[i]
ball = AnimatedBall(anim_canvas, x, y, dx, dy, radius, color)
balls.append(ball)
# 动画控制
animation_running = False
def animate():
if animation_running:
for ball in balls:
ball.move()
root.after(50, animate) # 每50毫秒更新一次
def start_animation():
global animation_running
if not animation_running:
animation_running = True
animate()
def stop_animation():
global animation_running
animation_running = False
# 动画控制按钮
anim_buttons = tk.Frame(animation_frame)
anim_buttons.pack(pady=5)
tk.Button(anim_buttons, text="创建球", command=create_balls).pack(side=tk.LEFT, padx=5)
tk.Button(anim_buttons, text="开始动画", command=start_animation).pack(side=tk.LEFT, padx=5)
tk.Button(anim_buttons, text="停止动画", command=stop_animation).pack(side=tk.LEFT, padx=5)
# 4. 图表绘制
print("\n4. 图表绘制演示")
chart_frame = tk.Frame(notebook)
notebook.add(chart_frame, text="图表绘制")
tk.Label(chart_frame, text="图表绘制演示",
font=("Arial", 14, "bold")).pack(pady=10)
# 创建图表画布
chart_canvas = tk.Canvas(chart_frame, width=700, height=450, bg="white", relief=tk.SUNKEN, bd=2)
chart_canvas.pack(pady=10)
def draw_bar_chart():
chart_canvas.delete("all")
# 示例数据
data = {"Python": 85, "Java": 70, "JavaScript": 65, "C++": 60, "Go": 45}
# 图表参数
margin = 50
chart_width = 600
chart_height = 350
bar_width = (chart_width - 2 * margin) // len(data)
max_value = max(data.values())
# 绘制坐标轴
chart_canvas.create_line(margin, margin, margin, chart_height + margin, width=2) # Y轴
chart_canvas.create_line(margin, chart_height + margin,
chart_width + margin, chart_height + margin, width=2) # X轴
# 绘制标题
chart_canvas.create_text(chart_width // 2 + margin, 20,
text="编程语言流行度", font=("Arial", 16, "bold"))
# 绘制柱状图
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FFEAA7"]
x = margin + 10
for i, (lang, value) in enumerate(data.items()):
# 计算柱子高度
bar_height = (value / max_value) * (chart_height - 20)
# 绘制柱子
chart_canvas.create_rectangle(x, chart_height + margin - bar_height,
x + bar_width - 20, chart_height + margin,
fill=colors[i % len(colors)], outline="black")
# 绘制标签
chart_canvas.create_text(x + (bar_width - 20) // 2, chart_height + margin + 15,
text=lang, font=("Arial", 10))
# 绘制数值
chart_canvas.create_text(x + (bar_width - 20) // 2,
chart_height + margin - bar_height - 10,
text=str(value), font=("Arial", 10, "bold"))
x += bar_width
def draw_line_chart():
chart_canvas.delete("all")
# 示例数据
months = ["1月", "2月", "3月", "4月", "5月", "6月"]
sales = [120, 150, 180, 200, 170, 220]
# 图表参数
margin = 50
chart_width = 600
chart_height = 350
# 绘制坐标轴
chart_canvas.create_line(margin, margin, margin, chart_height + margin, width=2)
chart_canvas.create_line(margin, chart_height + margin,
chart_width + margin, chart_height + margin, width=2)
# 绘制标题
chart_canvas.create_text(chart_width // 2 + margin, 20,
text="月度销售趋势", font=("Arial", 16, "bold"))
# 计算点的位置
x_step = chart_width // (len(months) - 1)
max_sales = max(sales)
min_sales = min(sales)
points = []
for i, sale in enumerate(sales):
x = margin + i * x_step
y = chart_height + margin - ((sale - min_sales) / (max_sales - min_sales)) * (chart_height - 20)
points.extend([x, y])
# 绘制数据点
chart_canvas.create_oval(x-4, y-4, x+4, y+4, fill="red", outline="darkred")
# 绘制数值标签
chart_canvas.create_text(x, y-15, text=str(sale), font=("Arial", 10, "bold"))
# 绘制月份标签
chart_canvas.create_text(x, chart_height + margin + 15,
text=months[i], font=("Arial", 10))
# 绘制折线
chart_canvas.create_line(points, fill="blue", width=3, smooth=True)
def draw_pie_chart():
chart_canvas.delete("all")
# 示例数据
data = {"桌面": 40, "移动": 35, "平板": 15, "其他": 10}
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4"]
# 饼图参数
center_x, center_y = 350, 225
radius = 120
# 绘制标题
chart_canvas.create_text(center_x, 50, text="设备使用分布", font=("Arial", 16, "bold"))
# 计算角度
total = sum(data.values())
start_angle = 0
for i, (category, value) in enumerate(data.items()):
# 计算扇形角度
extent = (value / total) * 360
# 绘制扇形
chart_canvas.create_arc(center_x - radius, center_y - radius,
center_x + radius, center_y + radius,
start=start_angle, extent=extent,
fill=colors[i % len(colors)], outline="white", width=2)
# 计算标签位置
label_angle = math.radians(start_angle + extent / 2)
label_x = center_x + (radius + 30) * math.cos(label_angle)
label_y = center_y + (radius + 30) * math.sin(label_angle)
# 绘制标签
chart_canvas.create_text(label_x, label_y,
text=f"{category}\n{value}%",
font=("Arial", 10), justify=tk.CENTER)
start_angle += extent
# 图表控制按钮
chart_buttons = tk.Frame(chart_frame)
chart_buttons.pack(pady=5)
tk.Button(chart_buttons, text="柱状图", command=draw_bar_chart).pack(side=tk.LEFT, padx=5)
tk.Button(chart_buttons, text="折线图", command=draw_line_chart).pack(side=tk.LEFT, padx=5)
tk.Button(chart_buttons, text="饼图", command=draw_pie_chart).pack(side=tk.LEFT, padx=5)
tk.Button(chart_buttons, text="清空", command=lambda: chart_canvas.delete("all")).pack(side=tk.LEFT, padx=5)
# 初始化
draw_basic_shapes()
create_balls()
draw_bar_chart()
print("\n画布控件演示窗口已创建,请查看GUI界面")
print("尝试不同的绘图和动画功能")
print("关闭窗口以继续下一个演示")
# 窗口关闭事件
def on_closing():
stop_animation() # 停止动画
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
# 启动事件循环
root.mainloop()
# 运行画布控件演示
canvas_demo()
# 5. 实际项目示例
# 5.1 简单计算器
import tkinter as tk
from tkinter import ttk, messagebox
import math
def calculator_app():
"""简单计算器应用"""
print("=== 计算器应用演示 ===")
# 创建主窗口
root = tk.Tk()
root.title("Python计算器")
root.geometry("350x500")
root.resizable(False, False)
# 设置样式
style = ttk.Style()
style.theme_use('clam')
# 计算器状态
current_input = tk.StringVar(value="0")
operator = ""
first_number = 0
should_reset = False
# 显示屏
display_frame = tk.Frame(root, bg="black", padx=10, pady=10)
display_frame.pack(fill=tk.X)
display = tk.Label(display_frame, textvariable=current_input,
font=("Arial", 24, "bold"), bg="black", fg="white",
anchor="e", padx=10, pady=10)
display.pack(fill=tk.X)
# 按钮框架
button_frame = tk.Frame(root, padx=10, pady=10)
button_frame.pack(fill=tk.BOTH, expand=True)
# 按钮样式配置
button_config = {
'font': ('Arial', 16, 'bold'),
'width': 4,
'height': 2
}
def clear_all():
"""清除所有"""
global operator, first_number, should_reset
current_input.set("0")
operator = ""
first_number = 0
should_reset = False
def clear_entry():
"""清除当前输入"""
current_input.set("0")
def backspace():
"""退格"""
current = current_input.get()
if len(current) > 1:
current_input.set(current[:-1])
else:
current_input.set("0")
def input_number(num):
"""输入数字"""
global should_reset
current = current_input.get()
if should_reset or current == "0":
current_input.set(str(num))
should_reset = False
else:
current_input.set(current + str(num))
def input_decimal():
"""输入小数点"""
global should_reset
current = current_input.get()
if should_reset:
current_input.set("0.")
should_reset = False
elif "." not in current:
current_input.set(current + ".")
def input_operator(op):
"""输入运算符"""
global operator, first_number, should_reset
try:
first_number = float(current_input.get())
operator = op
should_reset = True
except ValueError:
messagebox.showerror("错误", "无效的数字")
def calculate():
"""计算结果"""
global operator, first_number, should_reset
try:
second_number = float(current_input.get())
if operator == "+":
result = first_number + second_number
elif operator == "-":
result = first_number - second_number
elif operator == "×":
result = first_number * second_number
elif operator == "÷":
if second_number == 0:
messagebox.showerror("错误", "除数不能为零")
return
result = first_number / second_number
elif operator == "^":
result = first_number ** second_number
else:
return
# 格式化结果
if result == int(result):
current_input.set(str(int(result)))
else:
current_input.set(f"{result:.10g}")
operator = ""
should_reset = True
except ValueError:
messagebox.showerror("错误", "无效的数字")
except Exception as e:
messagebox.showerror("错误", f"计算错误: {str(e)}")
def calculate_sqrt():
"""计算平方根"""
try:
number = float(current_input.get())
if number < 0:
messagebox.showerror("错误", "负数不能开平方根")
return
result = math.sqrt(number)
current_input.set(f"{result:.10g}")
except ValueError:
messagebox.showerror("错误", "无效的数字")
def calculate_percent():
"""计算百分比"""
try:
number = float(current_input.get())
result = number / 100
current_input.set(f"{result:.10g}")
except ValueError:
messagebox.showerror("错误", "无效的数字")
def toggle_sign():
"""切换正负号"""
try:
number = float(current_input.get())
result = -number
if result == int(result):
current_input.set(str(int(result)))
else:
current_input.set(f"{result:.10g}")
except ValueError:
messagebox.showerror("错误", "无效的数字")
# 创建按钮
buttons = [
# 第一行
[("C", clear_all, "#FF6B6B"), ("CE", clear_entry, "#FF8E53"), ("⌫", backspace, "#FF8E53"), ("÷", lambda: input_operator("÷"), "#4ECDC4")],
# 第二行
[("7", lambda: input_number(7), "#E8E8E8"), ("8", lambda: input_number(8), "#E8E8E8"), ("9", lambda: input_number(9), "#E8E8E8"), ("×", lambda: input_operator("×"), "#4ECDC4")],
# 第三行
[("4", lambda: input_number(4), "#E8E8E8"), ("5", lambda: input_number(5), "#E8E8E8"), ("6", lambda: input_number(6), "#E8E8E8"), ("-", lambda: input_operator("-"), "#4ECDC4")],
# 第四行
[("1", lambda: input_number(1), "#E8E8E8"), ("2", lambda: input_number(2), "#E8E8E8"), ("3", lambda: input_number(3), "#E8E8E8"), ("+", lambda: input_operator("+"), "#4ECDC4")],
# 第五行
[("±", toggle_sign, "#D3D3D3"), ("0", lambda: input_number(0), "#E8E8E8"), (".", input_decimal, "#E8E8E8"), ("=", calculate, "#45B7D1")],
# 第六行
[("√", calculate_sqrt, "#96CEB4"), ("%", calculate_percent, "#96CEB4"), ("^", lambda: input_operator("^"), "#4ECDC4"), ("", None, "")]
]
for row_idx, row in enumerate(buttons):
for col_idx, (text, command, color) in enumerate(row):
if text: # 跳过空按钮
btn = tk.Button(button_frame, text=text, command=command,
bg=color, fg="black" if color != "#45B7D1" else "white",
activebackground=color, **button_config)
btn.grid(row=row_idx, column=col_idx, padx=2, pady=2, sticky="nsew")
# 配置网格权重
for i in range(4):
button_frame.grid_columnconfigure(i, weight=1)
for i in range(6):
button_frame.grid_rowconfigure(i, weight=1)
# 键盘绑定
def on_key_press(event):
key = event.char
if key.isdigit():
input_number(int(key))
elif key == ".":
input_decimal()
elif key in "+-*/":
op_map = {"+": "+", "-": "-", "*": "×", "/": "÷"}
input_operator(op_map[key])
elif key in "\r\n=": # Enter键或等号
calculate()
elif event.keysym == "BackSpace":
backspace()
elif event.keysym == "Escape":
clear_all()
root.bind("<Key>", on_key_press)
root.focus_set() # 设置焦点以接收键盘事件
print("\n计算器应用已启动")
print("支持鼠标点击和键盘输入")
print("键盘快捷键: 数字键、运算符、Enter(=)、Backspace、Esc(清除)")
# 启动事件循环
root.mainloop()
# 运行计算器应用
calculator_app()
# 5.2 文本编辑器
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, font
import os
def text_editor_app():
"""简单文本编辑器应用"""
print("=== 文本编辑器应用演示 ===")
# 创建主窗口
root = tk.Tk()
root.title("Python文本编辑器")
root.geometry("800x600")
# 应用状态
current_file = None
is_modified = False
def update_title():
"""更新窗口标题"""
title = "Python文本编辑器"
if current_file:
title += f" - {os.path.basename(current_file)}"
if is_modified:
title += " *"
root.title(title)
def mark_modified():
"""标记文件已修改"""
global is_modified
if not is_modified:
is_modified = True
update_title()
def mark_saved():
"""标记文件已保存"""
global is_modified
is_modified = False
update_title()
# 创建菜单栏
menubar = tk.Menu(root)
root.config(menu=menubar)
# 文件菜单
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="文件", menu=file_menu)
def new_file():
"""新建文件"""
global current_file
if is_modified:
response = messagebox.askyesnocancel("保存", "文件已修改,是否保存?")
if response is True:
save_file()
elif response is None:
return
text_area.delete(1.0, tk.END)
current_file = None
mark_saved()
def open_file():
"""打开文件"""
global current_file
if is_modified:
response = messagebox.askyesnocancel("保存", "文件已修改,是否保存?")
if response is True:
save_file()
elif response is None:
return
file_path = filedialog.askopenfilename(
title="打开文件",
filetypes=[("文本文件", "*.txt"), ("Python文件", "*.py"), ("所有文件", "*.*")]
)
if file_path:
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
text_area.delete(1.0, tk.END)
text_area.insert(1.0, content)
current_file = file_path
mark_saved()
except Exception as e:
messagebox.showerror("错误", f"无法打开文件: {str(e)}")
def save_file():
"""保存文件"""
global current_file
if current_file:
try:
content = text_area.get(1.0, tk.END + "-1c")
with open(current_file, 'w', encoding='utf-8') as file:
file.write(content)
mark_saved()
messagebox.showinfo("保存", "文件保存成功")
except Exception as e:
messagebox.showerror("错误", f"无法保存文件: {str(e)}")
else:
save_as_file()
def save_as_file():
"""另存为文件"""
global current_file
file_path = filedialog.asksaveasfilename(
title="另存为",
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("Python文件", "*.py"), ("所有文件", "*.*")]
)
if file_path:
try:
content = text_area.get(1.0, tk.END + "-1c")
with open(file_path, 'w', encoding='utf-8') as file:
file.write(content)
current_file = file_path
mark_saved()
messagebox.showinfo("保存", "文件保存成功")
except Exception as e:
messagebox.showerror("错误", f"无法保存文件: {str(e)}")
def exit_app():
"""退出应用"""
if is_modified:
response = messagebox.askyesnocancel("保存", "文件已修改,是否保存?")
if response is True:
save_file()
elif response is None:
return
root.quit()
file_menu.add_command(label="新建", command=new_file, accelerator="Ctrl+N")
file_menu.add_command(label="打开", command=open_file, accelerator="Ctrl+O")
file_menu.add_separator()
file_menu.add_command(label="保存", command=save_file, accelerator="Ctrl+S")
file_menu.add_command(label="另存为", command=save_as_file, accelerator="Ctrl+Shift+S")
file_menu.add_separator()
file_menu.add_command(label="退出", command=exit_app, accelerator="Ctrl+Q")
# 编辑菜单
edit_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="编辑", menu=edit_menu)
def undo():
try:
text_area.edit_undo()
except tk.TclError:
pass
def redo():
try:
text_area.edit_redo()
except tk.TclError:
pass
def cut():
try:
text_area.event_generate("<<Cut>>")
except tk.TclError:
pass
def copy():
try:
text_area.event_generate("<<Copy>>")
except tk.TclError:
pass
def paste():
try:
text_area.event_generate("<<Paste>>")
except tk.TclError:
pass
def select_all():
text_area.tag_add(tk.SEL, "1.0", tk.END)
text_area.mark_set(tk.INSERT, "1.0")
text_area.see(tk.INSERT)
def find_text():
"""查找文本"""
find_window = tk.Toplevel(root)
find_window.title("查找")
find_window.geometry("300x100")
find_window.resizable(False, False)
tk.Label(find_window, text="查找:").pack(pady=5)
search_var = tk.StringVar()
search_entry = tk.Entry(find_window, textvariable=search_var, width=30)
search_entry.pack(pady=5)
search_entry.focus()
def do_find():
search_text = search_var.get()
if search_text:
# 清除之前的高亮
text_area.tag_remove("found", "1.0", tk.END)
# 查找并高亮
start = "1.0"
while True:
pos = text_area.search(search_text, start, tk.END)
if not pos:
break
end = f"{pos}+{len(search_text)}c"
text_area.tag_add("found", pos, end)
start = end
# 设置高亮样式
text_area.tag_config("found", background="yellow")
# 跳转到第一个匹配项
first_match = text_area.search(search_text, "1.0", tk.END)
if first_match:
text_area.see(first_match)
text_area.mark_set(tk.INSERT, first_match)
tk.Button(find_window, text="查找", command=do_find).pack(pady=5)
edit_menu.add_command(label="撤销", command=undo, accelerator="Ctrl+Z")
edit_menu.add_command(label="重做", command=redo, accelerator="Ctrl+Y")
edit_menu.add_separator()
edit_menu.add_command(label="剪切", command=cut, accelerator="Ctrl+X")
edit_menu.add_command(label="复制", command=copy, accelerator="Ctrl+C")
edit_menu.add_command(label="粘贴", command=paste, accelerator="Ctrl+V")
edit_menu.add_separator()
edit_menu.add_command(label="全选", command=select_all, accelerator="Ctrl+A")
edit_menu.add_command(label="查找", command=find_text, accelerator="Ctrl+F")
# 格式菜单
format_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="格式", menu=format_menu)
def change_font():
"""更改字体"""
font_window = tk.Toplevel(root)
font_window.title("字体设置")
font_window.geometry("400x300")
current_font = font.Font(font=text_area['font'])
# 字体族
tk.Label(font_window, text="字体:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
font_family = tk.StringVar(value=current_font.actual()['family'])
font_listbox = tk.Listbox(font_window, height=6)
font_listbox.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
families = sorted(font.families())
for family in families:
font_listbox.insert(tk.END, family)
# 字体大小
tk.Label(font_window, text="大小:").grid(row=2, column=0, sticky="w", padx=5, pady=5)
size_var = tk.StringVar(value=str(current_font.actual()['size']))
size_entry = tk.Entry(font_window, textvariable=size_var, width=10)
size_entry.grid(row=2, column=1, padx=5, pady=5, sticky="w")
# 字体样式
bold_var = tk.BooleanVar(value=current_font.actual()['weight'] == 'bold')
italic_var = tk.BooleanVar(value=current_font.actual()['slant'] == 'italic')
tk.Checkbutton(font_window, text="粗体", variable=bold_var).grid(row=3, column=0, sticky="w", padx=5, pady=5)
tk.Checkbutton(font_window, text="斜体", variable=italic_var).grid(row=3, column=1, sticky="w", padx=5, pady=5)
def apply_font():
try:
selection = font_listbox.curselection()
if selection:
family = font_listbox.get(selection[0])
else:
family = current_font.actual()['family']
size = int(size_var.get())
weight = 'bold' if bold_var.get() else 'normal'
slant = 'italic' if italic_var.get() else 'roman'
new_font = font.Font(family=family, size=size, weight=weight, slant=slant)
text_area.config(font=new_font)
font_window.destroy()
except ValueError:
messagebox.showerror("错误", "请输入有效的字体大小")
tk.Button(font_window, text="应用", command=apply_font).grid(row=4, column=0, columnspan=2, pady=10)
format_menu.add_command(label="字体", command=change_font)
# 帮助菜单
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="帮助", menu=help_menu)
def show_about():
messagebox.showinfo("关于", "Python文本编辑器\n\n一个简单的文本编辑器示例\n使用Tkinter构建")
help_menu.add_command(label="关于", command=show_about)
# 创建工具栏
toolbar = tk.Frame(root, relief=tk.RAISED, bd=1)
toolbar.pack(side=tk.TOP, fill=tk.X)
# 工具栏按钮
tk.Button(toolbar, text="新建", command=new_file, width=6).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(toolbar, text="打开", command=open_file, width=6).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(toolbar, text="保存", command=save_file, width=6).pack(side=tk.LEFT, padx=2, pady=2)
ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
tk.Button(toolbar, text="剪切", command=cut, width=6).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(toolbar, text="复制", command=copy, width=6).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(toolbar, text="粘贴", command=paste, width=6).pack(side=tk.LEFT, padx=2, pady=2)
# 创建文本区域
text_frame = tk.Frame(root)
text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 文本控件
text_area = tk.Text(text_frame, wrap=tk.WORD, undo=True, font=("Consolas", 12))
# 滚动条
v_scrollbar = tk.Scrollbar(text_frame, orient=tk.VERTICAL, command=text_area.yview)
h_scrollbar = tk.Scrollbar(text_frame, orient=tk.HORIZONTAL, command=text_area.xview)
text_area.config(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
# 布局
text_area.grid(row=0, column=0, sticky="nsew")
v_scrollbar.grid(row=0, column=1, sticky="ns")
h_scrollbar.grid(row=1, column=0, sticky="ew")
text_frame.grid_rowconfigure(0, weight=1)
text_frame.grid_columnconfigure(0, weight=1)
# 状态栏
status_bar = tk.Frame(root, relief=tk.SUNKEN, bd=1)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 状态标签
line_col_label = tk.Label(status_bar, text="行: 1, 列: 1")
line_col_label.pack(side=tk.RIGHT, padx=5)
char_count_label = tk.Label(status_bar, text="字符数: 0")
char_count_label.pack(side=tk.RIGHT, padx=5)
def update_status(event=None):
"""更新状态栏"""
# 获取光标位置
cursor_pos = text_area.index(tk.INSERT)
line, col = cursor_pos.split('.')
line_col_label.config(text=f"行: {line}, 列: {int(col)+1}")
# 获取字符数
content = text_area.get(1.0, tk.END + "-1c")
char_count_label.config(text=f"字符数: {len(content)}")
# 绑定事件
text_area.bind("<KeyRelease>", update_status)
text_area.bind("<Button-1>", update_status)
text_area.bind("<KeyPress>", lambda e: mark_modified())
# 键盘快捷键
root.bind("<Control-n>", lambda e: new_file())
root.bind("<Control-o>", lambda e: open_file())
root.bind("<Control-s>", lambda e: save_file())
root.bind("<Control-Shift-S>", lambda e: save_as_file())
root.bind("<Control-q>", lambda e: exit_app())
root.bind("<Control-z>", lambda e: undo())
root.bind("<Control-y>", lambda e: redo())
root.bind("<Control-a>", lambda e: select_all())
root.bind("<Control-f>", lambda e: find_text())
# 窗口关闭事件
root.protocol("WM_DELETE_WINDOW", exit_app)
# 初始化状态
update_status()
update_title()
print("\n文本编辑器应用已启动")
print("支持基本的文本编辑功能")
print("包括文件操作、编辑操作、格式设置等")
# 启动事件循环
root.mainloop()
# 运行文本编辑器应用
text_editor_app()
# 6. GUI编程最佳实践
# 6.1 设计原则
# GUI设计最佳实践示例
def gui_best_practices():
"""GUI编程最佳实践演示"""
print("=== GUI编程最佳实践 ===")
# 1. 用户体验原则
print("\n1. 用户体验原则:")
print(" - 界面简洁直观")
print(" - 操作流程清晰")
print(" - 提供及时反馈")
print(" - 支持键盘快捷键")
print(" - 错误处理友好")
# 2. 布局设计
print("\n2. 布局设计:")
print(" - 使用合适的布局管理器")
print(" - 保持界面元素对齐")
print(" - 合理使用空白空间")
print(" - 响应式设计")
# 3. 代码组织
print("\n3. 代码组织:")
print(" - 分离界面和业务逻辑")
print(" - 使用类封装GUI组件")
print(" - 模块化设计")
print(" - 异常处理")
# 4. 性能优化
print("\n4. 性能优化:")
print(" - 避免阻塞UI线程")
print(" - 使用虚拟化处理大量数据")
print(" - 延迟加载")
print(" - 内存管理")
# 5. 可访问性
print("\n5. 可访问性:")
print(" - 支持键盘导航")
print(" - 提供工具提示")
print(" - 合适的颜色对比度")
print(" - 字体大小可调")
# 运行最佳实践演示
gui_best_practices()
# 6.2 常见问题和解决方案
def common_issues_solutions():
"""常见问题和解决方案"""
print("=== 常见问题和解决方案 ===")
# 1. 界面冻结问题
print("\n1. 界面冻结问题:")
print(" 问题: 长时间运行的任务导致界面无响应")
print(" 解决: 使用线程或after()方法")
# 示例:使用after()方法避免界面冻结
def long_task_example():
root = tk.Tk()
root.title("避免界面冻结示例")
progress = ttk.Progressbar(root, length=300, mode='determinate')
progress.pack(pady=20)
status_label = tk.Label(root, text="准备开始...")
status_label.pack(pady=10)
def simulate_work(step=0):
if step < 100:
progress['value'] = step
status_label.config(text=f"处理中... {step}%")
# 使用after()方法避免阻塞UI
root.after(50, lambda: simulate_work(step + 1))
else:
status_label.config(text="完成!")
tk.Button(root, text="开始任务", command=simulate_work).pack(pady=10)
return root
# 2. 内存泄漏问题
print("\n2. 内存泄漏问题:")
print(" 问题: 未正确销毁窗口和绑定")
print(" 解决: 正确使用destroy()和解绑事件")
# 3. 跨平台兼容性
print("\n3. 跨平台兼容性:")
print(" 问题: 不同操作系统显示效果不一致")
print(" 解决: 使用ttk主题控件,测试多平台")
# 4. 高DPI显示问题
print("\n4. 高DPI显示问题:")
print(" 问题: 在高DPI屏幕上显示模糊")
print(" 解决: 设置DPI感知")
# 示例:DPI感知设置
def set_dpi_awareness():
try:
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except:
pass # 非Windows系统或不支持
# 5. 数据绑定问题
print("\n5. 数据绑定问题:")
print(" 问题: 界面数据与模型数据不同步")
print(" 解决: 使用观察者模式或数据绑定框架")
# 运行常见问题演示
common_issues_solutions()
# 7. 学习建议和总结
# 7.1 学习路径
def learning_path():
"""GUI编程学习路径"""
print("=== GUI编程学习路径 ===")
learning_steps = [
{
"阶段": "基础入门",
"内容": [
"理解GUI编程概念",
"掌握基本控件使用",
"学习布局管理",
"练习事件处理"
],
"项目": "简单的表单应用"
},
{
"阶段": "进阶应用",
"内容": [
"高级控件使用",
"菜单和工具栏",
"对话框设计",
"数据展示控件"
],
"项目": "数据管理应用"
},
{
"阶段": "高级特性",
"内容": [
"自定义控件",
"主题和样式",
"多线程GUI",
"插件架构"
],
"项目": "完整的桌面应用"
},
{
"阶段": "专业开发",
"内容": [
"性能优化",
"跨平台部署",
"用户体验设计",
"测试和调试"
],
"项目": "商业级应用"
}
]
for i, step in enumerate(learning_steps, 1):
print(f"\n{i}. {step['阶段']}:")
for content in step['内容']:
print(f" - {content}")
print(f" 推荐项目: {step['项目']}")
# 运行学习路径演示
learning_path()
# 7.2 实践建议
def practice_suggestions():
"""实践建议"""
print("=== 实践建议 ===")
suggestions = {
"项目练习": [
"从简单项目开始,逐步增加复杂度",
"模仿现有应用的界面设计",
"关注用户体验和界面美观",
"完成完整的项目周期"
],
"代码质量": [
"遵循编码规范和最佳实践",
"编写清晰的注释和文档",
"进行代码重构和优化",
"使用版本控制管理代码"
],
"学习资源": [
"官方文档和教程",
"开源项目源码学习",
"技术博客和视频教程",
"参与社区讨论和交流"
],
"技能拓展": [
"学习其他GUI框架(PyQt, wxPython)",
"了解Web前端技术",
"掌握设计工具和原型制作",
"学习移动应用开发"
]
}
for category, items in suggestions.items():
print(f"\n{category}:")
for item in items:
print(f" • {item}")
# 运行实践建议演示
practice_suggestions()
# 7.3 总结
def chapter_summary():
"""本章总结"""
print("=== 第17天学习总结 ===")
summary_points = {
"核心概念": [
"GUI编程基础和事件驱动模型",
"Tkinter框架的基本使用",
"控件、布局和事件处理",
"用户界面设计原则"
],
"重要技能": [
"基本控件的使用和配置",
"布局管理器的选择和应用",
"事件处理和用户交互",
"菜单、对话框等高级组件"
],
"实际应用": [
"计算器应用开发",
"文本编辑器实现",
"数据展示和可视化",
"完整桌面应用构建"
],
"最佳实践": [
"代码组织和模块化设计",
"用户体验优化",
"性能和兼容性考虑",
"错误处理和调试技巧"
]
}
for category, points in summary_points.items():
print(f"\n{category}:")
for point in points:
print(f" ✓ {point}")
print("\n下一步学习方向:")
print(" • 深入学习高级GUI框架(PyQt/PySide)")
print(" • 探索Web应用开发(Flask/Django)")
print(" • 学习移动应用开发")
print(" • 掌握数据可视化技术")
print("\n恭喜完成第17天的学习!")
print("你已经掌握了Python GUI编程的基础知识和实践技能。")
print("继续练习和探索,构建更复杂和实用的桌面应用程序!")
# 运行总结
chapter_summary()
通过第17天的学习,你已经全面掌握了Python图形界面编程的核心概念和实践技能。从基础的控件使用到复杂的应用开发,从事件处理到用户体验设计,这些知识将为你开发桌面应用程序奠定坚实的基础。
记住,GUI编程不仅仅是技术实现,更重要的是理解用户需求,设计直观易用的界面。继续练习和探索,你将能够创建出功能强大、界面美观的桌面应用程序!