Python中基于Tkinter设计待办事项管理器(第26节)


在日常工作和生活中,我们经常需要记录和管理待办事项,以确保工作和生活的顺利进行。根据最新调查显示,使用专业任务管理工具的用户比不使用者的工作效率平均提升42%。本节教程将介绍如何通过Python编写一个简单而实用的待办事项管理程序,通过Python标准库中的Tkinter创建简单的GUI图形界面,并通过JSON进行数据存储,允许用户添加、编辑和删除待办事项。

1、待办事项管理器介绍

程序启动时,如果在当前脚本工作目录下存在“data.json”文件,则程序默认会从JSON文件中读取已保存的任务数据,如果不存在“data.json”文件,则会在当前脚本工作目录下自动创建“data.json”文件,用于保存任务数据。

主要功能介绍:

日期处理与日历选择:通过datetime库处理日期,通过tkcalendar库处理日历选择,tkcalendar库是Python的第三方库,用于创建和管理日期和时间的日历控件。

添加待办事项:点击“添加新任务”按钮后,在输入窗口中输入待办事项的详细信息,程序会将待办事项内容保存到本地的“data.json”文件中,下次打开程序时,会默认读取本地JSON文件中的数据。

编辑待办事项:在任务列表中可以通过左键双击鼠标,直接打开编辑待办事项的窗口,也可以点击界面中的“编辑任务”按钮,修改指定的任务。

删除待办事项:在任务列表中右击鼠标可以删除单条任务信息,也可以点击界面中的“删除任务”按钮,删除指定的任务。

优先级排序:优先级分为高/中/低三级,实现优先级顺序。

智能搜索功能: 支持多条件组合搜索 ,包括关键词搜索、任务状态、优先级。

任务列表颜色管理:在任务列表中,根据优先级、任务日期、完成状态突出显示不同的颜色。

2、待办事项管理器界面演示

Python中基于Tkinter设计待办事项管理器

Python中基于Tkinter设计待办事项管理器

Python中基于Tkinter设计待办事项管理器

3、代码实现

(1)安装tkcalendar库

首先,确保你已经安装了tkcalendar库。如果还没有安装,可以通过pip安装:

pip install tkcalendar

(2)待办事项管理器,完整代码如下所示:

动手练一练:

import tkinter as tk
from tkinter import ttk, messagebox
import os, json, locale, webbrowser
from datetime import datetime
from tkcalendar import DateEntry

# 获取当前脚本文件所在的目录
default_directory = os.path.dirname(os.path.abspath(__file__))
# 将当前脚本文件所在的目录设置为工作目录
os.chdir(default_directory)

# 定义空列表,用于从JSON文件导入全局任务列表
tasks = []
# 定义JSON文件名为“data.json”
JSON_FILE = "data.json"

# 设置系统的语言环境为中文
locale.setlocale(locale.LC_ALL, 'zh_CN.utf8')

# 定义成功提示框
def show_sucess(window, txt):
    add = tk.Toplevel(window)
    add.title("成功")
    screen_width, screen_height = add.maxsize()
    # 窗口的宽和高
    width = 300
    height = 100
    centre = '%dx%d+%d+%d' % (width, height, (screen_width - width) / 2,
                            (screen_height - height) / 2)
    add.geometry(centre)
    tk.Label(add, text=f'{txt}', font=('黑体', 14)).pack(pady=10)

    def confirmm():
        add.destroy()

    # 确定按钮
    tk.Button(add, text='确定', font=('黑体', 12), width=10, command=confirmm).pack(pady=10)

# 定义错误提示框
def show_error(window, txt):
    add = tk.Toplevel(window)
    add.title("错误")
    screen_width, screen_height = add.maxsize()
    # 窗口的宽和高
    width = 300
    height = 100
    centre = '%dx%d+%d+%d' % (width, height, (screen_width - width) / 2,
                            (screen_height - height) / 2)
    add.geometry(centre)
    tk.Label(add, text=f'{txt}', font=('黑体', 14)).pack(pady=10)

    def confirmm():
        add.destroy()

    # 确定按钮
    tk.Button(add, text='确定', font=('黑体', 12), width=10, command=confirmm).pack(pady=10)

# 创建主窗口界面
class TaskApp:
    def __init__(self, root):
        self.root = root
        self.root.title("待办事项管理器")

        # 设置窗口居中
        window_width = 950
        window_height = 710
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        x = (screen_width - window_width) / 2
        y = (screen_height - window_height) / 2
        self.root.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
        self.root.resizable(width=False, height=False)

        self.root.grid_columnconfigure(0, weight=1)
        self.root.grid_rowconfigure(0, weight=1)

        self.tasks_by_date = {}
        self.group_states = {}

        # 窗口样式配置
        self.style = ttk.Style()
        self.set_styles()

        # 创建主界面控件
        self.create_widgets()

        # 启动时间更新
        self.update_time()

    # 点击关闭窗口触发函数
    def window_close(self):
        # 弹出确认对话框
        if messagebox.askokcancel("退出", "你确定要退出吗?"):
            # 点击确定后关闭窗口
            self.root.destroy()

    # 定义样式函数
    def set_styles(self):
        self.style.configure("TButton", padding=6)
        self.style.configure("TLabel", padding=3)
        self.style.configure("TEntry", padding=3)
        self.style.configure("Blue.TLabel", foreground="blue", font=("宋体", 20))
        self.style.configure("Treeview", rowheight=25)   # 定义行高
        self.style.configure("Treeview.Heading", font=("宋体", 10, "bold"))

    # 创建“待办事项”界面控件
    def create_widgets(self):
        # 加载任务
        self.tasks_load()

        # 定义标题标签
        title_label = ttk.Label(self.root, text="待办事项管理器", font=('宋体', 20, 'bold'))
        title_label.pack(pady=10, padx=10)

        # 添加新任务区域
        add_frame = ttk.LabelFrame(self.root, text="添加新任务", padding="10")
        add_frame.pack(fill=tk.X, pady=10, padx=10)

        # 当前时间显示
        self.time_label = ttk.Label(add_frame, text="", style="Blue.TLabel")
        self.time_label.pack(pady=(0, 5))

        # 添加新任务按钮
        add_button = ttk.Button(add_frame, text="添加新任务", command=self.add_task)
        add_button.pack(padx=10, pady=10)

        # 任务列表区域
        list_frame = ttk.LabelFrame(self.root, text="任务列表", padding=5)
        list_frame.pack(fill=tk.X, padx=5, pady=5)

        # 任务搜索区域
        search_frame = ttk.Frame(list_frame)
        search_frame.pack(fill=tk.X, pady=5)

        # 关键词搜索
        ttk.Label(search_frame, text="搜索关键词:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.search_entry = ttk.Entry(search_frame, width=25)
        self.search_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)

        # 状态筛选
        ttk.Label(search_frame, text="任务状态:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        self.status_var = tk.StringVar(value="全部")
        self.status_combobox = ttk.Combobox(search_frame, textvariable=self.status_var, values=["全部", "已完成", "未完成"], state="readonly")
        self.status_combobox.grid(row=0, column=3, padx=5, pady=5, sticky=tk.W)

        # 优先级筛选
        ttk.Label(search_frame, text="优先级:").grid(row=0, column=4, padx=5, pady=5, sticky=tk.W)
        self.priority_filter_var = tk.StringVar(value="全部")
        self.priority_combobox = ttk.Combobox(search_frame, textvariable=self.priority_filter_var, values=["全部", "高", "中", "低"],
                                    state="readonly")
        self.priority_combobox.grid(row=0, column=5, padx=5, pady=5, sticky=tk.W)

        self.search_button = ttk.Button(search_frame, text="搜索", command=self.update_list)
        self.search_button.grid(row=0, column=6, padx=8, pady=5, sticky=tk.W)

        # 排序区域
        sort_frame = ttk.Frame(list_frame)
        sort_frame.pack(fill=tk.X, pady=5)

        ttk.Label(sort_frame, text="排序字段:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.sort_var = tk.StringVar(value="创建时间")
        self.sort_combobox = ttk.Combobox(sort_frame, textvariable=self.sort_var,
                                values=["任务名称", "优先级", "状态", "截止日期", "创建时间"],
                                state="readonly")
        self.sort_combobox.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)

        ttk.Label(sort_frame, text="排序方向:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        self.order_var = tk.StringVar(value="升序")
        self.order_combobox = ttk.Combobox(sort_frame, textvariable=self.order_var, values=["升序", "降序"], state="readonly")
        self.order_combobox.grid(row=0, column=3, padx=5, pady=5, sticky=tk.W)

        ttk.Button(sort_frame, text="任务排序", command=self.update_list).grid(row=0, column=4, padx=10, pady=5, sticky=tk.E)

         # 任务统计
        self.task_count = ttk.Label(sort_frame, text=f"共 {len(tasks)} 个任务")
        self.task_count.grid(row=0, column=5, padx=5, pady=5, sticky=tk.E)

        # 利用Treeview控件显示任务列表
        columns = ("ID", "任务名称", "创建时间", "截止日期", "状态", "提醒设置", "描述")
        self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="browse")

        # 设置表头
        for col in columns:
            self.tree.heading(col, text=col)
            # 设置列的宽度
            self.tree.column(col, width=130, anchor="center")

        # 初始化列表
        self.update_list()

        # 为列表添加标签样式
        def fixed_map(option):
            return [elm for elm in style.map("Treeview", query_opt=option)
                    if elm[:2] != ("!disabled", "!selected")]
        style = ttk.Style()
        style.map("Treeview",
                        foreground=fixed_map("foreground"),
                        background=fixed_map("background"))
        self.tree.tag_configure("high", foreground="red")
        self.tree.tag_configure("medium", foreground="orange")
        self.tree.tag_configure("low", foreground="green")
        self.tree.tag_configure("overdue", background="#ffe6e6")  # 粉红色为过期任务
        self.tree.tag_configure("today", background="#fff6cc")  # 淡黄色为今日到期任务
        self.tree.tag_configure("soon", background="#e6f2ff")  # 淡蓝色为即将到期任务

        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 设置滚动条
        scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.tree.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.configure(yscrollcommand=scrollbar.set)

        # 鼠标双击任务列表任意一行,可以触发修改任务页面
        def double_click(event):
            # 获取被双击的项的IID
            iid = self.tree.focus()
            if iid == '':
                return  # 如果没有选中任何一行,则直接返回

            # 选中指定的行
            self.tree.selection_set((iid,))
            self.edit_task()

        # 绑定鼠标双击事件
        self.tree.bind("<Double-1>", double_click)

        # 鼠标右键点击任务列表任意一行,就会显示“删除任务”按钮
        def right_click(event):
            # 获取当前焦点项的iid
            iid = self.tree.focus()
            # 选中指定的行
            self.tree.selection_set((iid,))

            if iid:
                # 弹出菜单选项
                menu = tk.Menu(self.tree, tearoff=0)
                menu.add_command(label="删除任务", command=self.delete_task)
                menu.tk_popup(event.x_root, event.y_root)

        # 绑定鼠标右键点击事件
        self.tree.bind("<Button-3>", right_click)

        # 功能按钮
        button_frame = ttk.Frame(self.root)
        button_frame.pack(fill=tk.X, pady=5)
        refresh_button = ttk.Button(button_frame, text="刷新列表", command=self.update_list)
        refresh_button.pack(side=tk.LEFT, padx=10)
        add_button = ttk.Button(button_frame, text="添加新任务", command=self.add_task)
        add_button.pack(side=tk.LEFT, padx=10)
        edit_button = ttk.Button(button_frame, text="编辑任务", command=self.edit_task)
        edit_button.pack(side=tk.LEFT, padx=10)
        delete_button = ttk.Button(button_frame, text="删除任务", command=self.delete_task)
        delete_button.pack(side=tk.LEFT, padx=10)
        quit_button = ttk.Button(button_frame, text="退出", command=self.window_close)
        quit_button.pack(side=tk.LEFT, padx=10)
        about_button = ttk.Button(button_frame, text="关于程序", command=self.app_about)
        about_button.pack(side=tk.RIGHT, padx=10)
        help_button = ttk.Button(button_frame, text="程序讲解", command=self.help_window)
        help_button.pack(side=tk.RIGHT, padx=10)

    # 添加任务
    def add_task(self):
        self.add_window = tk.Toplevel(self.root)
        self.add_window.title("添加任务")

        # 设置窗口居中
        window_width = 500
        window_height = 440
        screen_width = self.add_window.winfo_screenwidth()
        screen_height = self.add_window.winfo_screenheight()
        x = (screen_width - window_width) / 2
        y = (screen_height - window_height) / 2
        self.add_window.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
        self.add_window.resizable(width=False, height=False)

        # 创建标题标签控件
        show_title = tk.Label(self.add_window, text=">>>添加任务<<<", bg = "white", fg = "black", font = ("宋体", 18), bd = '0', anchor = 'center')
        show_title.place(x = 50, y = 30, width = 400, height = 50)

        # 任务名称
        ttk.Label(self.add_window, text="任务名称:", width=10, anchor=tk.E).place(x = 75, y = 120, anchor = 'nw')
        self.name_entry = ttk.Entry(self.add_window, width=40)
        self.name_entry.place(x = 150, y = 120, anchor = 'nw')

        # 优先级
        ttk.Label(self.add_window, text="优先级:", width=10, anchor=tk.E).place(x = 75, y = 170, anchor = 'nw')
        self.priority_input_var = tk.StringVar(value="中")
        self.priority_combobox = ttk.Combobox(self.add_window, textvariable=self.priority_input_var, values=["高", "中", "低"], state="readonly")
        self.priority_combobox.place(x = 150, y = 170, anchor = 'nw')

        # 任务描述
        ttk.Label(self.add_window, text="任务描述:", width=10, anchor=tk.E).place(x = 75, y = 220, anchor = 'nw')
        self.description_entry = ttk.Entry(self.add_window, width=40)
        self.description_entry.place(x = 150, y = 220, anchor = 'nw')

        # 截止日期(日历)
        ttk.Label(self.add_window, text="截止日期:", width=10, anchor=tk.E).place(x = 75, y = 270, anchor = 'nw')
        self.deaddate_calendar = DateEntry(self.add_window, date_pattern="yyyy-mm-dd", locale='zh_CN')
        self.deaddate_calendar.set_date(datetime.now())
        self.deaddate_calendar.place(x = 150, y = 270, anchor = 'nw')

        # 提醒设置
        ttk.Label(self.add_window, text="提醒设置:", width=10, anchor=tk.E).place(x = 75, y = 320, anchor = 'nw')
        self.reminder_var = tk.StringVar(value="不提醒")
        reminder_comb = ttk.Combobox(self.add_window, textvariable=self.reminder_var,
                                    values=["不提醒", "提前5分钟", "提前15分钟", "提前30分钟", "提前1小时", "提前1天"],
                                    state="readonly")
        reminder_comb.place(x = 150, y = 320, anchor = 'nw')

        # 确认添加按钮
        add_button = ttk.Button(self.add_window, text="确认添加任务", command=self.add_command)
        add_button.place(x = 200, y = 370, anchor = 'nw')

    # 更新当前时间显示
    def update_time(self):
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.time_label.config(text=f"当前时间: {current_time}")
        self.root.after(1000, self.update_time)  # 每秒更新一次

    # 通过JSON文件加载任务
    def tasks_load(self):
        global tasks
        if os.path.exists(JSON_FILE):
            try:
                with open(JSON_FILE, "r", encoding="utf-8") as file:
                    tasks = json.load(file)
            except Exception as e:
                show_error(self.root, f"加载任务失败: {e}")
                tasks = []
        else:
            tasks = []

    # 通过JSON文件保存任务内容
    def tasks_save(self):
        try:
            with open(JSON_FILE, "w", encoding="utf-8") as file:
                json.dump(tasks, file, ensure_ascii=False, indent=4)
        except Exception as e:
            show_error(self.root, f"保存任务失败: {e}")

    # 更新任务列表显示,包括筛选或排序方式
    def update_list(self):
        # 清空表格
        for item in self.tree.get_children():
            self.tree.delete(item)

        # 任务列表筛选
        tasks_filtered = self.apply_filter()
        # 任务列表排序
        tasks_sorted = self.apply_sort(tasks_filtered)

        # 遍历任务列表重新插入数据
        for index, task in enumerate(tasks_sorted, start=1):
            # 根据优先级显示不同颜色
            tag = ""
            if task["priority"] == "高":
                tag = "high"
            elif task["priority"] == "中":
                tag = "medium"
            else:
                tag = "low"

            # 通过截止日期设置特殊样式
            if task["deaddate"] and not task["status"]:
                deaddate_date = datetime.strptime(task["deaddate"], "%Y-%m-%d").date()
                today = datetime.now().date()
                if deaddate_date < today:
                    tag = "overdue"
                elif deaddate_date == today:
                    tag = "today"
                elif (deaddate_date - today).days <= 3:
                    tag = "soon"

            # 添加数据
            self.tree.insert(
                "",
                tk.END,
                values=(
                    index,
                    task["name"],
                    task["created"],
                    task["deaddate"],
                    "√" if task["status"] else "×",
                    task["reminder"],
                    task["description"]
                ),
                tags=(tag,)
            )

        # 更新任务统计
        self.task_count.config(text=f"共 {len(tasks_filtered)} 个任务")

        # 设置列表宽度
        for col in self.tree["columns"]:
            self.tree.column(col, width=130, anchor="center")


    # 定义筛选条件,包括多字段模糊匹配+状态+优先级
    def apply_filter(self):
        filter_text = self.search_entry.get().lower()
        filter_status = self.status_var.get()
        filter_priority = self.priority_filter_var.get()

        filtered = []
        for task in tasks:
            # 名称/描述/提醒模糊匹配
            name_match = filter_text in task["name"].lower()
            describe_match = filter_text in task["description"].lower()
            reminder_match = filter_text in task["reminder"].lower()

            # 状态筛选(全部/已完成/未完成)
            status_match = (filter_status == "全部") or (task["status"] == (filter_status == "已完成"))

            # 优先级筛选(全部/高/中/低)
            priority_match = (filter_priority == "全部") or (task["priority"] == filter_priority)

            if (name_match or describe_match or reminder_match) and status_match and priority_match:
                filtered.append(task)
        return filtered

    # 定义列表排序,包括按选择的字段和顺序
    def apply_sort(self, tasks_list):
        sort_by = self.sort_var.get()
        sort_order = self.order_var.get()

        # 自定义排序键
        def sort_key(task):
            if sort_by == "任务名称":
                return task["name"]
            elif sort_by == "优先级":
                return {"高": 1, "中": 2, "低": 3}[task["priority"]]
            elif sort_by == "状态":
                return task["status"]  # 已完成(True)排在后面
            elif sort_by == "截止日期":
                return datetime.strptime(task["deaddate"], "%Y-%m-%d") if task["deaddate"] else datetime.max
            elif sort_by == "创建时间":
                return datetime.strptime(task["created"], "%Y-%m-%d %H:%M:%S")
            return 0  # 默认按ID排序

        # 排序
        tasks_sorted = sorted(tasks_list, key=sort_key, reverse=(sort_order == "降序"))
        return tasks_sorted

    # 添加新任务
    def add_command(self):
        task_name = self.name_entry.get().strip()
        priority = self.priority_input_var.get()
        description = self.description_entry.get().strip()
        deaddate = self.deaddate_calendar.get_date().strftime("%Y-%m-%d")
        reminder = self.reminder_var.get()

        if not task_name:
            show_error(self.root, "任务名称不能为空!")
            return

        new_task = {
            "name": task_name,
            "priority": priority,
            "status": False,
            "description": description,
            "deaddate": deaddate,
            "reminder": reminder,
            "created": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }

        # 添加任务到列表
        tasks.append(new_task)
        self.tasks_save()

        # 刷新列表并清空输入框
        self.update_list()
        self.clear_inputs()

        # 显示成功提示
        messagebox.showinfo("成功", f"任务 '{task_name}' 已添加")

    # 清空输入框
    def clear_inputs(self):
        self.name_entry.delete(0, tk.END)
        self.description_entry.delete(0, tk.END)
        self.deaddate_calendar.set_date(datetime.now())
        self.reminder_var.set("不提醒")
        self.priority_input_var.set("中")  # 优先级选择重置

    # 删除选中任务
    def delete_task(self):
        selected_items = self.tree.selection()
        if not selected_items:
            show_error(self.root, "请选择要删除的任务!")
            return

        if len(selected_items) == 1:
            task_id = self.tree.item(selected_items[0], "values")[0]
            task_name = self.tree.item(selected_items[0], "values")[1]
            confirm_msg = f"确定要删除任务 '{task_name}' (ID: {task_id}) 吗?"
        else:
            confirm_msg = f"确定要删除选中的 {len(selected_items)} 个任务吗?"

        if messagebox.askyesno("确认删除", confirm_msg):
            # 为了避免索引混乱,按表格索引反向删除
            for item in reversed(selected_items):
                task_index = self.tree.index(item)
                tasks.pop(task_index)
            self.tasks_save()
            self.update_list()

    # 编辑选中任务
    def edit_task(self):
        selected_items = self.tree.selection()
        if len(selected_items) != 1:
            show_error(self.root, "请选择一条任务进行编辑!")
            return

        selected_item = selected_items[0]
        task_index = self.tree.index(selected_item)
        task = tasks[task_index]

        self.edit_window = tk.Toplevel(self.root)
        self.edit_window.title("编辑任务")

        # 设置窗口居中
        window_width = 450
        window_height = 500
        screen_width = self.edit_window.winfo_screenwidth()
        screen_height = self.edit_window.winfo_screenheight()
        x = (screen_width - window_width) / 2
        y = (screen_height - window_height) / 2
        self.edit_window.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
        self.edit_window.resizable(width=False, height=False)

        # 创建标题标签控件
        show_title = tk.Label(self.edit_window, text=">>>编辑任务<<<", bg = "white", fg = "black", font = ("宋体", 18), bd = '0', anchor = 'center')
        show_title.place(x = 50, y = 30, width = 350, height = 50)

        # 任务名称
        ttk.Label(self.edit_window, text="任务名称:").place(x = 75, y = 120, anchor = 'nw')
        name_edit = ttk.Entry(self.edit_window, width=30)
        name_edit.insert(0, task["name"])
        name_edit.place(x = 150, y = 120, anchor = 'nw')

        # 优先级
        ttk.Label(self.edit_window, text="优先级:").place(x = 75, y = 170, anchor = 'nw')
        priority_edit = ttk.Combobox(self.edit_window, values=["高", "中", "低"], state="readonly")
        priority_edit.set(task["priority"])
        priority_edit.place(x = 150, y = 170, anchor = 'nw')

        # 状态
        ttk.Label(self.edit_window, text="状态:").place(x = 75, y = 220, anchor = 'nw')
        status_edit = ttk.Combobox(self.edit_window, values=["已完成", "未完成"], state="readonly")
        status_edit.set("已完成" if task["status"] else "未完成")
        status_edit.place(x = 150, y = 220, anchor = 'nw')

        # 描述
        ttk.Label(self.edit_window, text="描述:").place(x = 75, y = 270, anchor = 'nw')
        describe_edit = ttk.Entry(self.edit_window, width=30)
        describe_edit.insert(0, task["description"])
        describe_edit.place(x = 150, y = 270, anchor = 'nw')

        # 截止日期(日历选择)
        ttk.Label(self.edit_window, text="截止日期:").place(x = 75, y = 320, anchor = 'nw')
        deaddate_edit = DateEntry(self.edit_window, date_pattern="yyyy-mm-dd", locale='zh_CN')
        deaddate_edit.set_date(datetime.strptime(task["deaddate"], "%Y-%m-%d"))
        deaddate_edit.place(x = 150, y = 320, anchor = 'nw')

        # 提醒设置
        ttk.Label(self.edit_window, text="提醒设置:").place(x = 75, y = 370, anchor = 'nw')
        reminder_edit = ttk.Combobox(self.edit_window,
                                    values=["不提醒", "提前5分钟", "提前15分钟", "提前30分钟", "提前1小时", "提前1天"],
                                    state="readonly")
        reminder_edit.set(task["reminder"])
        reminder_edit.place(x = 150, y = 370, anchor = 'nw')

        # 保存编辑
        def save_edit():
            name = name_edit.get().strip()
            if not name:
                show_error(self.root, "任务名称不能为空!")
                return

            task["name"] = name
            task["priority"] = priority_edit.get()
            task["status"] = (status_edit.get() == "已完成")
            task["description"] = describe_edit.get().strip()
            task["deaddate"] = deaddate_edit.get_date().strftime("%Y-%m-%d")
            task["reminder"] = reminder_edit.get()

            self.tasks_save()
            self.update_list()
            self.edit_window.destroy()

        ttk.Button(self.edit_window, text="保存", command=save_edit).place(x = 90, y = 430, anchor = 'nw')
        ttk.Button(self.edit_window, text="取消", command=self.edit_window.destroy).place(x = 260, y = 430, anchor = 'nw')

    # 关于程序页面
    def app_about(self):
        about = tk.Tk()
        about.title('关于程序')

        # 设置窗口居中
        window_width = 400
        window_height = 380
        screen_width = about.winfo_screenwidth()
        screen_height = about.winfo_screenheight()
        x = (screen_width - window_width) / 2
        y = (screen_height - window_height) / 2
        about.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
        about.resizable(width=False, height=False)

        about_frame = tk.Frame(about, width=490, height=520)
        about_frame.pack()
        tk.Label(about_frame, text='待办事项管理器', font=("宋体", 16)).place(x=120, y=20)
        tk.Label(about_frame, text='使用编程语言:Python', font=("宋体", 14)).place(x=50, y=70)
        tk.Label(about_frame, text='保存数据方式:保存为JSON文件', font=("宋体", 14)).place(x=50, y=120)
        tk.Label(about_frame, text='JSON文件名:data.json', font=("宋体", 14)).place(x=50, y=170)
        tk.Label(about_frame, text='修改任务:在任务列表直接双击鼠标', font=("宋体", 14)).place(x=50, y=220)
        tk.Label(about_frame, text='删除任务:在任务列表右击鼠标', font=("宋体", 14)).place(x=50, y=270)
        tk.Label(about_frame, text='创作者:www.pyhint.com', font=("宋体", 14)).place(x=50, y=320)
        about.mainloop()

    # 程序讲解页面
    def help_window(self):
        webbrowser.open("https://www.pyhint.com/article/159.html")

    def run(self):
        self.root.mainloop()

# 当前模块直接被执行
if __name__ == "__main__":
    root = tk.Tk()
    app = TaskApp(root)
    app.run()