第180集:GUI项目实战
一、项目概述
在本集中,我们将结合之前学过的所有GUI编程知识,开发一个完整的任务管理器应用。这个应用将具备以下功能:
- 任务的添加、编辑、删除和标记完成
- 任务的分类管理
- 任务的搜索和过滤
- 任务数据的保存和加载
- 直观的用户界面(包含菜单栏、工具栏、状态栏)
- 美观的样式设计
通过这个项目,我们将学习如何将零散的GUI知识整合起来,开发一个功能完整、用户友好的应用程序。
二、项目设计
1. 功能模块设计
任务管理器应用主要包含以下功能模块:
| 模块名称 | 主要功能 |
|---|---|
| 界面布局 | 设计应用的整体布局,包括菜单、工具栏、主界面和状态栏 |
| 任务管理 | 实现任务的添加、编辑、删除、标记完成等核心功能 |
| 数据管理 | 负责任务数据的保存(JSON格式)和加载 |
| 搜索过滤 | 实现任务的搜索和按状态/分类过滤 |
| 界面交互 | 处理用户的各种交互操作,如点击、输入等 |
2. 界面设计
应用界面将采用经典的桌面应用布局:
- 菜单栏:包含文件、编辑、查看、帮助等菜单
- 工具栏:提供常用操作的快捷按钮
- 主界面:
- 左侧:任务分类列表
- 右侧:任务列表(显示任务标题、优先级、截止日期等)
- 底部:任务详情编辑区域
- 状态栏:显示当前任务数量和应用状态
三、项目实现
1. 项目结构
task_manager/
├── main.py # 主程序入口
├── task_manager.py # 任务管理器类
├── task.py # 任务类定义
├── data/ # 数据文件目录
│ └── tasks.json # 任务数据文件
└── README.md # 项目说明2. 任务类定义
首先,我们定义一个Task类来表示任务:
# task.py
import json
from datetime import datetime
class Task:
def __init__(self, title, description="", priority="中", category="默认", due_date=None, completed=False):
self.id = datetime.now().strftime("%Y%m%d%H%M%S%f") # 生成唯一ID
self.title = title
self.description = description
self.priority = priority # 高、中、低
self.category = category
self.due_date = due_date
self.completed = completed
self.created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def to_dict(self):
"""将任务转换为字典,用于保存到JSON文件"""
return {
"id": self.id,
"title": self.title,
"description": self.description,
"priority": self.priority,
"category": self.category,
"due_date": self.due_date,
"completed": self.completed,
"created_at": self.created_at
}
@classmethod
def from_dict(cls, data):
"""从字典创建任务对象"""
task = cls(
title=data["title"],
description=data["description"],
priority=data["priority"],
category=data["category"],
due_date=data["due_date"],
completed=data["completed"]
)
task.id = data["id"]
task.created_at = data["created_at"]
return task3. 任务管理器类
接下来,我们实现任务管理器类,负责任务的管理和数据的保存加载:
# task_manager.py
import json
import os
from task import Task
class TaskManager:
def __init__(self, data_file="data/tasks.json"):
self.data_file = data_file
self.tasks = []
self.load_tasks()
def load_tasks(self):
"""从JSON文件加载任务数据"""
if os.path.exists(self.data_file):
try:
with open(self.data_file, "r", encoding="utf-8") as f:
tasks_data = json.load(f)
self.tasks = [Task.from_dict(task_data) for task_data in tasks_data]
except Exception as e:
print(f"加载任务数据失败:{e}")
self.tasks = []
else:
# 如果目录不存在,创建目录
os.makedirs(os.path.dirname(self.data_file), exist_ok=True)
self.tasks = []
def save_tasks(self):
"""将任务数据保存到JSON文件"""
try:
with open(self.data_file, "w", encoding="utf-8") as f:
tasks_data = [task.to_dict() for task in self.tasks]
json.dump(tasks_data, f, ensure_ascii=False, indent=4)
return True
except Exception as e:
print(f"保存任务数据失败:{e}")
return False
def add_task(self, task):
"""添加新任务"""
self.tasks.append(task)
return self.save_tasks()
def update_task(self, task_id, **kwargs):
"""更新任务信息"""
for task in self.tasks:
if task.id == task_id:
for key, value in kwargs.items():
if hasattr(task, key):
setattr(task, key, value)
return self.save_tasks()
return False
def delete_task(self, task_id):
"""删除任务"""
self.tasks = [task for task in self.tasks if task.id != task_id]
return self.save_tasks()
def get_tasks(self, category=None, completed=None, search_query=None):
"""获取任务列表,支持过滤"""
filtered_tasks = self.tasks
# 按分类过滤
if category and category != "全部":
filtered_tasks = [task for task in filtered_tasks if task.category == category]
# 按完成状态过滤
if completed is not None:
filtered_tasks = [task for task in filtered_tasks if task.completed == completed]
# 按搜索关键词过滤
if search_query:
search_query = search_query.lower()
filtered_tasks = [task for task in filtered_tasks if
search_query in task.title.lower() or
search_query in task.description.lower()]
return filtered_tasks
def get_categories(self):
"""获取所有任务分类"""
categories = set([task.category for task in self.tasks])
return ["全部"] + list(categories)4. 主程序实现
最后,我们实现主程序,创建GUI界面并将所有功能整合起来:
# main.py
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
from task_manager import TaskManager
from task import Task
from datetime import datetime
class TaskManagerApp:
def __init__(self, root):
self.root = root
self.root.title("任务管理器")
self.root.geometry("1000x700")
# 初始化任务管理器
self.task_manager = TaskManager()
# 设置主题样式
self.style = ttk.Style()
self.style.theme_use("clam")
# 创建界面组件
self.create_menu()
self.create_toolbar()
self.create_main_frame()
self.create_statusbar()
# 刷新界面
self.refresh_categories()
self.refresh_tasks()
def create_menu(self):
"""创建菜单栏"""
menubar = tk.Menu(self.root)
# 文件菜单
file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="新建任务", accelerator="Ctrl+N", command=self.add_task)
file_menu.add_command(label="保存", accelerator="Ctrl+S", command=self.save_tasks)
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.root.quit)
menubar.add_cascade(label="文件", menu=file_menu)
# 编辑菜单
edit_menu = tk.Menu(menubar, tearoff=0)
edit_menu.add_command(label="编辑任务", accelerator="Ctrl+E", command=self.edit_task)
edit_menu.add_command(label="删除任务", accelerator="Delete", command=self.delete_task)
menubar.add_cascade(label="编辑", menu=edit_menu)
# 查看菜单
view_menu = tk.Menu(menubar, tearoff=0)
self.show_completed_var = tk.BooleanVar(value=True)
view_menu.add_checkbutton(label="显示已完成任务", variable=self.show_completed_var, command=self.refresh_tasks)
menubar.add_cascade(label="查看", menu=view_menu)
# 帮助菜单
help_menu = tk.Menu(menubar, tearoff=0)
help_menu.add_command(label="关于", command=self.show_about)
menubar.add_cascade(label="帮助", menu=help_menu)
# 绑定快捷键
self.root.bind("<Control-n>", lambda event: self.add_task())
self.root.bind("<Control-s>", lambda event: self.save_tasks())
self.root.bind("<Control-e>", lambda event: self.edit_task())
self.root.bind("<Delete>", lambda event: self.delete_task())
self.root.config(menu=menubar)
def create_toolbar(self):
"""创建工具栏"""
toolbar = ttk.Frame(self.root)
toolbar.pack(fill=tk.X, padx=5, pady=5)
# 添加按钮
self.add_btn = ttk.Button(toolbar, text="新建任务", command=self.add_task)
self.add_btn.pack(side=tk.LEFT, padx=2)
self.edit_btn = ttk.Button(toolbar, text="编辑任务", command=self.edit_task)
self.edit_btn.pack(side=tk.LEFT, padx=2)
self.delete_btn = ttk.Button(toolbar, text="删除任务", command=self.delete_task)
self.delete_btn.pack(side=tk.LEFT, padx=2)
self.complete_btn = ttk.Button(toolbar, text="标记完成", command=self.toggle_complete)
self.complete_btn.pack(side=tk.LEFT, padx=2)
# 分隔线
ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
# 搜索框
ttk.Label(toolbar, text="搜索:").pack(side=tk.LEFT, padx=2)
self.search_var = tk.StringVar()
self.search_entry = ttk.Entry(toolbar, textvariable=self.search_var, width=30)
self.search_entry.pack(side=tk.LEFT, padx=2)
self.search_entry.bind("<KeyRelease>", lambda event: self.refresh_tasks())
def create_main_frame(self):
"""创建主界面框架"""
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 左侧:分类列表
left_frame = ttk.LabelFrame(main_frame, text="任务分类")
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
self.category_var = tk.StringVar(value="全部")
self.category_listbox = tk.Listbox(left_frame, width=25, height=20)
self.category_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 分类列表滚动条
category_scrollbar = ttk.Scrollbar(left_frame, orient=tk.VERTICAL, command=self.category_listbox.yview)
category_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.category_listbox.config(yscrollcommand=category_scrollbar.set)
# 分类列表点击事件
self.category_listbox.bind("<<ListboxSelect>>", self.on_category_select)
# 右侧:任务列表和详情
right_frame = ttk.Frame(main_frame)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 任务列表
task_list_frame = ttk.LabelFrame(right_frame, text="任务列表")
task_list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 任务列表树视图
columns = ("id", "title", "priority", "due_date", "completed")
self.task_tree = ttk.Treeview(task_list_frame, columns=columns, show="headings", height=15)
# 设置列标题
self.task_tree.heading("id", text="ID", width=50)
self.task_tree.heading("title", text="标题", width=200)
self.task_tree.heading("priority", text="优先级", width=80)
self.task_tree.heading("due_date", text="截止日期", width=100)
self.task_tree.heading("completed", text="状态", width=80)
# 设置列对齐
for col in columns:
self.task_tree.column(col, anchor=tk.CENTER)
# 添加滚动条
tree_scrollbar = ttk.Scrollbar(task_list_frame, orient=tk.VERTICAL, command=self.task_tree.yview)
self.task_tree.configure(yscrollcommand=tree_scrollbar.set)
self.task_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
tree_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 任务列表点击事件
self.task_tree.bind("<<TreeviewSelect>>", self.on_task_select)
# 任务详情
detail_frame = ttk.LabelFrame(right_frame, text="任务详情")
detail_frame.pack(fill=tk.BOTH, padx=5, pady=5)
# 标题
ttk.Label(detail_frame, text="标题:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=3)
self.title_var = tk.StringVar()
ttk.Entry(detail_frame, textvariable=self.title_var, width=50).grid(row=0, column=1, padx=5, pady=3)
# 优先级
ttk.Label(detail_frame, text="优先级:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=3)
self.priority_var = tk.StringVar(value="中")
priority_combo = ttk.Combobox(detail_frame, textvariable=self.priority_var, values=["高", "中", "低"], state="readonly")
priority_combo.grid(row=1, column=1, padx=5, pady=3, sticky=tk.W)
# 截止日期
ttk.Label(detail_frame, text="截止日期:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=3)
self.due_date_var = tk.StringVar()
ttk.Entry(detail_frame, textvariable=self.due_date_var, width=20).grid(row=2, column=1, padx=5, pady=3, sticky=tk.W)
# 分类
ttk.Label(detail_frame, text="分类:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=3)
self.category_entry_var = tk.StringVar()
ttk.Entry(detail_frame, textvariable=self.category_entry_var, width=20).grid(row=3, column=1, padx=5, pady=3, sticky=tk.W)
# 描述
ttk.Label(detail_frame, text="描述:").grid(row=4, column=0, sticky=tk.NW, padx=5, pady=3)
self.description_var = tk.StringVar()
description_text = tk.Text(detail_frame, height=5, width=50)
description_text.grid(row=4, column=1, padx=5, pady=3, sticky=tk.W)
self.description_text = description_text
# 保存按钮
ttk.Button(detail_frame, text="保存修改", command=self.save_task_changes).grid(row=5, column=1, padx=5, pady=10, sticky=tk.E)
def create_statusbar(self):
"""创建状态栏"""
self.status_var = tk.StringVar(value="就绪")
statusbar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
statusbar.pack(side=tk.BOTTOM, fill=tk.X)
def refresh_categories(self):
"""刷新分类列表"""
categories = self.task_manager.get_categories()
self.category_listbox.delete(0, tk.END)
for category in categories:
self.category_listbox.insert(tk.END, category)
# 默认选择"全部"
if "全部" in categories:
self.category_listbox.selection_set(categories.index("全部"))
def refresh_tasks(self):
"""刷新任务列表"""
# 清空任务列表
for item in self.task_tree.get_children():
self.task_tree.delete(item)
# 获取当前选择的分类
selected_category = "全部"
if self.category_listbox.curselection():
selected_index = self.category_listbox.curselection()[0]
selected_category = self.category_listbox.get(selected_index)
# 获取搜索关键词
search_query = self.search_var.get()
# 获取任务列表
completed_filter = None if self.show_completed_var.get() else False
tasks = self.task_manager.get_tasks(
category=selected_category,
completed=completed_filter,
search_query=search_query
)
# 添加任务到列表
for task in tasks:
status = "已完成" if task.completed else "未完成"
self.task_tree.insert("", tk.END, values=(
task.id,
task.title,
task.priority,
task.due_date or "",
status
))
# 更新状态栏
self.status_var.set(f"共 {len(tasks)} 个任务")
def on_category_select(self, event):
"""分类列表选择事件"""
self.refresh_tasks()
def on_task_select(self, event):
"""任务列表选择事件"""
selected_items = self.task_tree.selection()
if not selected_items:
return
# 获取选中的任务ID
selected_item = selected_items[0]
task_id = self.task_tree.item(selected_item)["values"][0]
# 查找对应的任务
for task in self.task_manager.tasks:
if task.id == task_id:
# 显示任务详情
self.title_var.set(task.title)
self.priority_var.set(task.priority)
self.due_date_var.set(task.due_date or "")
self.category_entry_var.set(task.category)
self.description_text.delete(1.0, tk.END)
self.description_text.insert(tk.END, task.description)
break
def add_task(self):
"""添加新任务"""
# 创建一个默认任务
task = Task(
title="新任务",
description="",
priority="中",
category="默认",
due_date=None,
completed=False
)
# 添加到任务管理器
if self.task_manager.add_task(task):
# 刷新界面
self.refresh_categories()
self.refresh_tasks()
self.status_var.set("任务添加成功")
def edit_task(self):
"""编辑选中的任务"""
selected_items = self.task_tree.selection()
if not selected_items:
messagebox.showwarning("警告", "请先选择一个任务")
return
# 这里可以打开一个编辑对话框,现在我们直接使用详情区域进行编辑
messagebox.showinfo("提示", "请在下方任务详情区域修改任务信息,然后点击保存修改按钮")
def delete_task(self):
"""删除选中的任务"""
selected_items = self.task_tree.selection()
if not selected_items:
messagebox.showwarning("警告", "请先选择一个任务")
return
# 确认删除
if messagebox.askyesno("确认", "确定要删除选中的任务吗?"):
# 获取选中的任务ID
selected_item = selected_items[0]
task_id = self.task_tree.item(selected_item)["values"][0]
# 删除任务
if self.task_manager.delete_task(task_id):
# 刷新界面
self.refresh_categories()
self.refresh_tasks()
# 清空详情区域
self.clear_task_details()
self.status_var.set("任务删除成功")
def toggle_complete(self):
"""切换任务完成状态"""
selected_items = self.task_tree.selection()
if not selected_items:
messagebox.showwarning("警告", "请先选择一个任务")
return
# 获取选中的任务ID
selected_item = selected_items[0]
task_id = self.task_tree.item(selected_item)["values"][0]
# 查找对应的任务
for task in self.task_manager.tasks:
if task.id == task_id:
# 切换完成状态
new_status = not task.completed
if self.task_manager.update_task(task_id, completed=new_status):
# 刷新界面
self.refresh_tasks()
self.status_var.set(f"任务已{"标记为完成" if new_status else "标记为未完成"}")
break
def save_task_changes(self):
"""保存任务修改"""
selected_items = self.task_tree.selection()
if not selected_items:
messagebox.showwarning("警告", "请先选择一个任务")
return
# 获取选中的任务ID
selected_item = selected_items[0]
task_id = self.task_tree.item(selected_item)["values"][0]
# 获取修改后的任务信息
title = self.title_var.get()
priority = self.priority_var.get()
due_date = self.due_date_var.get() or None
category = self.category_entry_var.get() or "默认"
description = self.description_text.get(1.0, tk.END).strip()
# 验证标题
if not title:
messagebox.showwarning("警告", "任务标题不能为空")
return
# 更新任务
if self.task_manager.update_task(task_id,
title=title,
priority=priority,
due_date=due_date,
category=category,
description=description):
# 刷新界面
self.refresh_categories()
self.refresh_tasks()
self.status_var.set("任务修改保存成功")
def save_tasks(self):
"""手动保存任务数据"""
if self.task_manager.save_tasks():
self.status_var.set("任务数据保存成功")
else:
self.status_var.set("任务数据保存失败")
def clear_task_details(self):
"""清空任务详情区域"""
self.title_var.set("")
self.priority_var.set("中")
self.due_date_var.set("")
self.category_entry_var.set("")
self.description_text.delete(1.0, tk.END)
def show_about(self):
"""显示关于对话框"""
about_text = "任务管理器 v1.0\n\n一个基于Python Tkinter开发的简单任务管理应用\n\n用于管理日常任务,支持添加、编辑、删除任务,\n以及按分类、状态进行过滤和搜索。"
messagebox.showinfo("关于任务管理器", about_text)
# 运行应用
if __name__ == "__main__":
root = tk.Tk()
app = TaskManagerApp(root)
root.mainloop()四、项目测试和运行
1. 创建项目文件
将上面的代码分别保存为三个文件:
task.py:任务类定义task_manager.py:任务管理器类main.py:主程序入口
2. 运行应用
在命令行中执行以下命令运行应用:
python main.py3. 测试功能
运行应用后,测试以下功能:
- 添加任务:点击"新建任务"按钮或按Ctrl+N
- 编辑任务:选择一个任务,在详情区域修改后点击"保存修改"
- 删除任务:选择一个任务,点击"删除任务"按钮或按Delete键
- 标记完成:选择一个任务,点击"标记完成"按钮
- 分类管理:在任务详情中修改分类,分类列表会自动更新
- 搜索过滤:在搜索框中输入关键词,任务列表会实时过滤
- 显示/隐藏已完成任务:在"查看"菜单中切换
- 保存数据:任务数据会自动保存到
data/tasks.json文件
五、项目打包
我们可以使用PyInstaller将任务管理器应用打包成可执行文件:
pyinstaller -F -w -i "icon.ico" main.py其中,icon.ico是应用的图标文件(可选)。
六、项目扩展
这个任务管理器应用可以进一步扩展以下功能:
- 任务提醒:添加任务截止日期提醒功能
- 数据备份:支持任务数据的导入和导出
- 主题切换:允许用户切换应用的主题样式
- 任务统计:添加任务完成情况的统计图表
- 多人协作:支持任务的共享和协作编辑
- 云同步:将任务数据同步到云端
七、总结
通过本集的学习,我们开发了一个功能完整的任务管理器应用,整合了之前学过的所有GUI编程知识,包括:
- 窗口和界面布局设计
- 菜单和工具栏的创建
- 事件处理和快捷键绑定
- 数据的保存和加载(JSON格式)
- 列表和树视图的使用
- 对话框和消息提示
- 界面样式和用户体验优化
这个项目展示了如何将零散的GUI知识整合起来,开发一个实用的桌面应用程序。通过不断练习和扩展,你可以开发出更加复杂和功能丰富的Python GUI应用。
至此,我们的GUI编程部分已经全部完成。接下来,我们将进入Python测试与调试的学习,学习如何编写和运行测试,以及如何调试Python程序。