—— tkinter:Python 内置 GUI 工具包
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
# 创建主窗口
root = tk.Tk()
root.title("我的第一个 GUI")
root.geometry("600x400")
# 标签
label = tk.Label(root, text="Hello, tkinter!", font=("Arial", 16))
label.pack(pady=20)
# 按钮
def on_click():
messagebox.showinfo("提示", "按钮被点击了!")
button = tk.Button(root, text="点我", command=on_click, bg="#34d399", fg="white")
button.pack(pady=10)
# 输入框
entry = tk.Entry(root, width=30)
entry.pack(pady=5)
entry.insert(0, "请输入...")
# 文本框
text = tk.Text(root, width=50, height=10)
text.pack(pady=5)
# 运行主循环
# root.mainloop() # 取消注释即可运行
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("布局示例")
# ===== pack 布局 =====
frame_pack = tk.LabelFrame(root, text="Pack布局")
frame_pack.pack(fill="x", padx=10, pady=5)
tk.Label(frame_pack, text="顶部", bg="#34d399").pack(fill="x")
tk.Label(frame_pack, text="中间", bg="#7dd3fc").pack(fill="x", expand=True)
tk.Label(frame_pack, text="底部", bg="#fbbf24").pack(fill="x")
# ===== grid 布局 =====
frame_grid = tk.LabelFrame(root, text="Grid布局")
frame_grid.pack(fill="x", padx=10, pady=5)
tk.Label(frame_grid, text="用户名:").grid(row=0, column=0, sticky="e", padx=5, pady=5)
tk.Entry(frame_grid, width=20).grid(row=0, column=1, padx=5, pady=5)
tk.Label(frame_grid, text="密码:").grid(row=1, column=0, sticky="e", padx=5, pady=5)
tk.Entry(frame_grid, width=20, show="*").grid(row=1, column=1, padx=5, pady=5)
tk.Button(frame_grid, text="登录").grid(row=2, column=0, columnspan=2, pady=10)
# grid 配置列权重
frame_grid.columnconfigure(1, weight=1)
# root.mainloop()
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("ttk 组件")
# Notebook(标签页)
notebook = ttk.Notebook(root)
notebook.pack(fill="both", expand=True, padx=10, pady=10)
# 标签页1:表格
tab1 = ttk.Frame(notebook)
notebook.add(tab1, text="数据表")
tree = ttk.Treeview(tab1, columns=("name", "age", "city"), show="headings")
tree.heading("name", text="姓名")
tree.heading("age", text="年龄")
tree.heading("city", text="城市")
tree.column("name", width=100)
tree.column("age", width=80)
tree.column("city", width=100)
data = [("张三", 25, "北京"), ("李四", 30, "上海"), ("王五", 28, "广州")]
for item in data:
tree.insert("", "end", values=item)
tree.pack(fill="both", expand=True)
# 标签页2:进度条
tab2 = ttk.Frame(notebook)
notebook.add(tab2, text="进度")
progress = ttk.Progressbar(tab2, mode="determinate", length=300)
progress.pack(pady=20)
progress["value"] = 65
# 标签页3:组合框
tab3 = ttk.Frame(notebook)
notebook.add(tab3, text="选择")
combo = ttk.Combobox(tab3, values=["Python", "Java", "Go", "Rust"])
combo.set("Python")
combo.pack(pady=20)
# Scale 滑块
scale = ttk.Scale(tab3, from_=0, to=100, orient="horizontal")
scale.pack(pady=10, fill="x", padx=20)
# root.mainloop()
"""注意:以下代码需要在有显示器的环境中运行。
在无头服务器上无法运行 GUI,但逻辑代码可以验证。
"""
import tkinter as tk
from tkinter import ttk
import json
from pathlib import Path
class ClipboardManager:
"""剪贴板历史管理器"""
HISTORY_FILE = Path.home() / ".clipboard_history.json"
def __init__(self):
self.history = self._load_history()
def _load_history(self):
if self.HISTORY_FILE.exists():
with open(self.HISTORY_FILE) as f:
return json.load(f)
return []
def _save_history(self):
with open(self.HISTORY_FILE, "w") as f:
json.dump(self.history, f, ensure_ascii=False, indent=2)
def add(self, text):
if text and text not in self.history:
self.history.insert(0, text)
self.history = self.history[:100] # 最多100条
self._save_history()
def search(self, keyword):
return [h for h in self.history if keyword.lower() in h.lower()]
def clear(self):
self.history = []
self._save_history()
# GUI 界面(需要显示器)
class ClipboardApp:
def __init__(self):
self.manager = ClipboardManager()
self.root = tk.Tk()
self.root.title("剪贴板管理器")
self.root.geometry("400x500")
# 搜索框
search_frame = ttk.Frame(self.root)
search_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(search_frame, text="搜索:").pack(side="left")
self.search_var = tk.StringVar()
self.search_var.trace_add("write", lambda *_: self.filter_list())
search_entry = ttk.Entry(search_frame, textvariable=self.search_var)
search_entry.pack(side="left", fill="x", expand=True, padx=5)
# 列表
self.listbox = tk.Listbox(self.root, font=("Courier", 10))
self.listbox.pack(fill="both", expand=True, padx=10, pady=5)
self.listbox.bind("<<ListboxSelect>>", self.on_select)
# 按钮
btn_frame = ttk.Frame(self.root)
btn_frame.pack(fill="x", padx=10, pady=5)
ttk.Button(btn_frame, text="清除历史", command=self.clear).pack(side="right")
self.refresh_list()
def refresh_list(self):
self.listbox.delete(0, "end")
for item in self.manager.history:
self.listbox.insert("end", item[:50])
def filter_list(self):
keyword = self.search_var.get()
items = self.manager.search(keyword) if keyword else self.manager.history
self.listbox.delete(0, "end")
for item in items:
self.listbox.insert("end", item[:50])
def on_select(self, event):
selection = self.listbox.curselection()
if selection:
idx = selection[0]
text = self.manager.history[idx]
self.root.clipboard_clear()
self.root.clipboard_append(text)
def clear(self):
self.manager.clear()
self.refresh_list()
def run(self):
self.root.mainloop()
#!/usr/bin/env python3
"""第34课 GUI开发验证"""
import tkinter as tk
from tkinter import ttk
def test_tkinter_import():
"""tkinter 导入测试"""
assert hasattr(tk, 'Tk')
assert hasattr(tk, 'Label')
assert hasattr(tk, 'Button')
assert hasattr(tk, 'Entry')
assert hasattr(tk, 'Text')
print("✅ tkinter导入测试通过")
def test_ttk_components():
"""ttk 组件测试"""
assert hasattr(ttk, 'Button')
assert hasattr(ttk, 'Entry')
assert hasattr(ttk, 'Label')
assert hasattr(ttk, 'Treeview')
assert hasattr(ttk, 'Notebook')
assert hasattr(ttk, 'Combobox')
assert hasattr(ttk, 'Progressbar')
print("✅ ttk组件测试通过")
def test_clipboard_manager():
"""剪贴板管理器逻辑测试"""
import json
import tempfile
import os
class TestManager:
def __init__(self):
self.history = []
def add(self, text):
if text and text not in self.history:
self.history.insert(0, text)
self.history = self.history[:100]
def search(self, keyword):
return [h for h in self.history if keyword.lower() in h.lower()]
def clear(self):
self.history = []
mgr = TestManager()
mgr.add("Hello World")
mgr.add("Python GUI")
mgr.add("Hello Python")
assert len(mgr.history) == 3
assert mgr.search("Hello") == ["Hello Python", "Hello World"]
mgr.add("Hello World") # 重复不添加
assert len(mgr.history) == 3
mgr.clear()
assert len(mgr.history) == 0
print("✅ 剪贴板管理器测试通过")
def test_widget_creation():
"""控件创建测试(无需显示)"""
root = tk.Tk()
root.withdraw() # 不显示窗口
label = tk.Label(root, text="Test")
button = tk.Button(root, text="Click")
entry = tk.Entry(root)
assert label.cget("text") == "Test"
assert button.cget("text") == "Click"
root.destroy()
print("✅ 控件创建测试通过")
if __name__ == "__main__":
test_tkinter_import()
test_ttk_components()
test_clipboard_manager()
test_widget_creation()
print("\n🎉 第34课全部验证通过!")
import tkinter as tk
from tkinter import ttk, filedialog
from pathlib import Path
import threading
class FileSearchApp:
"""文件搜索工具"""
def __init__(self):
self.root = tk.Tk()
self.root.title("文件搜索工具")
self.root.geometry("700x500")
self._build_ui()
def _build_ui(self):
# 搜索栏
search_frame = ttk.Frame(self.root)
search_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(search_frame, text="目录:").pack(side="left")
self.dir_var = tk.StringVar(value=str(Path.home()))
ttk.Entry(search_frame, textvariable=self.dir_var, width=40).pack(side="left", padx=5)
ttk.Button(search_frame, text="浏览", command=self._browse).pack(side="left")
ttk.Label(search_frame, text="关键词:").pack(side="left", padx=(10,0))
self.keyword_var = tk.StringVar()
ttk.Entry(search_frame, textvariable=self.keyword_var, width=20).pack(side="left", padx=5)
ttk.Button(search_frame, text="搜索", command=self._search).pack(side="left")
# 结果表格
columns = ("path", "size", "modified")
self.tree = ttk.Treeview(self.root, columns=columns, show="headings")
self.tree.heading("path", text="路径")
self.tree.heading("size", text="大小")
self.tree.heading("modified", text="修改时间")
self.tree.column("path", width=400)
self.tree.pack(fill="both", expand=True, padx=10, pady=5)
self.status_var = tk.StringVar(value="就绪")
ttk.Label(self.root, textvariable=self.status_var).pack(fill="x", padx=10, pady=2)
def _browse(self):
d = filedialog.askdirectory()
if d: self.dir_var.set(d)
def _search(self):
directory = self.dir_var.get()
keyword = self.keyword_var.get()
self.tree.delete(*self.tree.get_children())
self.status_var.set("搜索中...")
def do_search():
count = 0
for f in Path(directory).rglob("*"):
if f.is_file() and (not keyword or keyword.lower() in f.name.lower()):
size = f.stat().st_size
from datetime import datetime
mtime = datetime.fromtimestamp(f.stat().st_mtime).strftime("%Y-%m-%d %H:%M")
self.tree.insert("", "end", values=(str(f), f"{size:,}", mtime))
count += 1
self.status_var.set(f"找到 {count} 个文件")
threading.Thread(target=do_search, daemon=True).start()
def run(self):
self.root.mainloop()
import tkinter as tk
from tkinter import ttk, messagebox
class PomodoroTimer:
"""番茄钟计时器"""
def __init__(self):
self.root = tk.Tk()
self.root.title("番茄钟")
self.root.geometry("300x250")
self.running = False
self.remaining = 25 * 60
self.mode = "work"
self._build_ui()
def _build_ui(self):
self.time_var = tk.StringVar(value="25:00")
tk.Label(self.root, textvariable=self.time_var,
font=("Courier", 48, "bold"), fg="#34d399", bg="#0f172a").pack(pady=20)
self.mode_var = tk.StringVar(value="工作时间")
tk.Label(self.root, textvariable=self.mode_var, font=("Arial", 14)).pack()
btn_frame = ttk.Frame(self.root)
btn_frame.pack(pady=15)
self.start_btn = ttk.Button(btn_frame, text="开始", command=self._toggle)
self.start_btn.pack(side="left", padx=5)
ttk.Button(btn_frame, text="重置", command=self._reset).pack(side="left", padx=5)
setting_frame = ttk.Frame(self.root)
setting_frame.pack(pady=5)
ttk.Label(setting_frame, text="工作:").pack(side="left")
self.work_min = tk.IntVar(value=25)
ttk.Spinbox(setting_frame, from_=1, to=60, textvariable=self.work_min, width=3).pack(side="left")
ttk.Label(setting_frame, text="分钟 休息:").pack(side="left")
self.break_min = tk.IntVar(value=5)
ttk.Spinbox(setting_frame, from_=1, to=30, textvariable=self.break_min, width=3).pack(side="left")
def _toggle(self):
if self.running:
self.running = False
self.start_btn.config(text="继续")
else:
self.running = True
self.start_btn.config(text="暂停")
self._tick()
def _tick(self):
if not self.running: return
if self.remaining <= 0:
self.running = False
messagebox.showinfo("番茄钟", "时间到!")
return
mins, secs = divmod(self.remaining, 60)
self.time_var.set(f"{mins:02d}:{secs:02d}")
self.remaining -= 1
self.root.after(1000, self._tick)
def _reset(self):
self.running = False
self.remaining = self.work_min.get() * 60
self.start_btn.config(text="开始")
mins, secs = divmod(self.remaining, 60)
self.time_var.set(f"{mins:02d}:{secs:02d}")
def run(self):
self.root.mainloop()