基于wxPython的TodoList任务管理器开发详解

前言

在日常工作和学习中,任务管理是提高效率的重要工具。本文将详细介绍如何使用Python的wxPython GUI框架开发一个功能完善的TodoList任务管理器。这个项目不仅适合wxPython初学者学习GUI编程,也能作为实用工具直接使用。
C:\pythoncode\new\todolist_app.py

项目概述

核心功能

  • ✅ 任务的增删改查(CRUD)操作
  • ✅ 任务优先级管理(高/中/低)
  • ✅ 任务分类管理
  • ✅ 截止日期设置
  • ✅ 多条件筛选和搜索
  • ✅ 任务完成状态切换
  • ✅ 数据持久化存储
  • ✅ 实时统计信息显示
  • ✅ 键盘快捷操作

技术栈

  • Python 3.x
  • wxPython:跨平台GUI框架
  • JSON:数据存储格式

代码结构分析

1. 模块导入

import wx
import wx.adv
import wx.lib.mixins.listctrl as listmix
import json
import os
from datetime import datetime, date

关键模块说明:

  • wx:wxPython核心模块
  • wx.adv:高级控件模块,提供DatePickerCtrl等控件
  • wx.lib.mixins.listctrl:ListCtrl的混入类,提供自动列宽调整功能
  • json:用于数据的序列化和持久化
  • datetime:处理日期时间

2. TodoListCtrl类 - 自定义列表控件

class TodoListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT | wx.LC_SINGLE_SEL)
        listmix.ListCtrlAutoWidthMixin.__init__(self)

设计要点:

  1. 多重继承:同时继承wx.ListCtrlListCtrlAutoWidthMixin

    • wx.ListCtrl:提供列表显示功能
    • ListCtrlAutoWidthMixin:自动调整最后一列宽度,优化显示效果
  2. 样式设置

    • wx.LC_REPORT:报表视图模式,支持多列显示
    • wx.LC_SINGLE_SEL:单选模式,一次只能选择一个项目
  3. 列定义

    self.InsertColumn(0, "完成", width=50)    # 完成状态标记
    self.InsertColumn(1, "任务", width=250)   # 任务名称
    self.InsertColumn(2, "优先级", width=70)  # 优先级
    self.InsertColumn(3, "分类", width=100)   # 任务分类
    self.InsertColumn(4, "截止日期", width=100) # 截止日期
    self.InsertColumn(5, "创建日期", width=100) # 创建时间
    

3. TaskDialog类 - 任务编辑对话框

这是整个程序中最复杂的类,负责任务的添加和编辑。

3.1 初始化方法
def __init__(self, parent, title="添加任务", task_data=None):
    super().__init__(None, title=title, size=(450, 400))
    
    self.task_data = task_data
    self.init_ui()
    
    if task_data:
        self.load_task_data(task_data)
    
    # 绑定键盘事件
    self.Bind(wx.EVT_CHAR_HOOK, self.on_key_press)

关键点:

  • parent参数设为None:使对话框独立显示,不受主窗口约束
  • task_data:如果提供则为编辑模式,否则为添加模式
  • EVT_CHAR_HOOK:全局键盘事件,实现回车键导航功能
3.2 键盘导航实现
def on_key_press(self, event):
    keycode = event.GetKeyCode()
    
    # 回车键模拟Tab键(切换焦点)
    if keycode == wx.WXK_RETURN or keycode == wx.WXK_NUMPAD_ENTER:
        focused = self.FindFocus()
        
        # 如果焦点在确定按钮上,则提交对话框
        if isinstance(focused, wx.Button) and focused.GetId() == wx.ID_OK:
            self.EndModal(wx.ID_OK)
            return
        
        # 如果焦点在多行文本框,允许换行
        if isinstance(focused, wx.TextCtrl) and focused.HasFlag(wx.TE_MULTILINE):
            event.Skip()
            return
        
        # 否则模拟Tab键,切换到下一个控件
        focused.Navigate()
    else:
        event.Skip()

实现原理:

  1. 捕获回车键事件
  2. 判断当前焦点控件类型
  3. 根据不同控件采取不同行为:
    • 确定按钮:提交对话框
    • 多行文本框:允许换行
    • 其他控件:切换到下一个控件
3.3 UI布局

对话框使用垂直BoxSizer布局,包含以下控件:

任务名称输入框:

self.task_input = wx.TextCtrl(panel, size=(300, -1))

任务描述(多行文本):

self.desc_input = wx.TextCtrl(panel, size=(300, 80), 
                              style=wx.TE_MULTILINE)

优先级选择:

self.priority_choice = wx.Choice(panel, choices=["高", "中", "低"])
self.priority_choice.SetSelection(1)  # 默认选择"中"

日期选择器:

self.date_picker = wx.adv.DatePickerCtrl(panel, 
                                         style=wx.adv.DP_DROPDOWN | wx.adv.DP_SHOWCENTURY)
self.no_date_cb = wx.CheckBox(panel, label="无截止日期")

设计亮点:

  • 提供"无截止日期"复选框,满足不同需求
  • 日期选择器使用下拉样式,操作便捷
3.4 数据加载和获取

加载任务数据(编辑模式):

def load_task_data(self, data):
    self.task_input.SetValue(data.get('name', ''))
    self.desc_input.SetValue(data.get('description', ''))
    
    priority_map = {"高": 0, "中": 1, "低": 2}
    self.priority_choice.SetSelection(priority_map.get(data.get('priority', '中'), 1))
    
    self.category_input.SetValue(data.get('category', ''))
    
    if data.get('due_date'):
        try:
            due_date = datetime.strptime(data['due_date'], "%Y-%m-%d").date()
            wx_date = wx.DateTime()
            wx_date.Set(due_date.day, due_date.month - 1, due_date.year)
            self.date_picker.SetValue(wx_date)
        except:
            pass
    
    self.no_date_cb.SetValue(not data.get('due_date'))

注意事项:

  • wxPython的月份从0开始,需要减1
  • 使用try-except处理日期解析异常

获取任务数据:

def get_task_data(self):
    due_date = None
    if not self.no_date_cb.GetValue():
        wx_date = self.date_picker.GetValue()
        due_date = f"{wx_date.GetYear()}-{wx_date.GetMonth()+1:02d}-{wx_date.GetDay():02d}"
    
    return {
        'name': self.task_input.GetValue(),
        'description': self.desc_input.GetValue(),
        'priority': self.priority_choice.GetStringSelection(),
        'category': self.category_input.GetValue(),
        'due_date': due_date,
        'completed': self.task_data.get('completed', False) if self.task_data else False,
        'created_date': self.task_data.get('created_date') if self.task_data else datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }

数据结构设计:

  • 使用字典存储任务信息
  • 日期格式统一为YYYY-MM-DD
  • 创建时间精确到秒

4. TodoListFrame类 - 主窗口

这是程序的核心类,管理整个应用的UI和逻辑。

4.1 初始化
def __init__(self):
    super().__init__(None, title="Todo List 任务管理器", size=(900, 650))
    
    self.tasks = []
    self.data_file = "todolist_data.json"
    self.load_data()
    
    self.init_ui()
    self.Centre()
    self.Show()

初始化流程:

  1. 创建窗口(900×650像素)
  2. 初始化任务列表
  3. 加载持久化数据
  4. 构建UI界面
  5. 居中显示窗口
4.2 UI布局结构

主窗口采用垂直布局,从上到下包含:

1. 工具栏(操作按钮):

toolbar = wx.BoxSizer(wx.HORIZONTAL)

add_btn = wx.Button(panel, label="添加任务")
edit_btn = wx.Button(panel, label="编辑任务")
delete_btn = wx.Button(panel, label="删除任务")
toggle_btn = wx.Button(panel, label="切换完成状态")

2. 筛选栏:

# 搜索框
self.search_input = wx.TextCtrl(panel, size=(150, -1))

# 分类筛选
self.category_filter = wx.Choice(panel, choices=["全部"])

# 优先级筛选
self.priority_filter = wx.Choice(panel, choices=["全部", "高", "中", "低"])

# 状态筛选
self.status_filter = wx.Choice(panel, choices=["全部", "未完成", "已完成"])

3. 任务列表:

self.list_ctrl = TodoListCtrl(panel)
vbox.Add(self.list_ctrl, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
  • proportion=1:占据剩余所有空间
  • wx.EXPAND:填充水平和垂直方向

4. 统计信息栏:

self.stats_text = wx.StaticText(panel, label="")
4.3 任务管理功能

添加任务:

def on_add_task(self, event):
    dlg = TaskDialog(self, "添加任务")
    if dlg.ShowModal() == wx.ID_OK:
        task_data = dlg.get_task_data()
        if task_data['name']:
            self.tasks.append(task_data)
            self.save_data()
            self.refresh_list()
        else:
            wx.MessageBox("任务名称不能为空!", "错误", wx.OK | wx.ICON_ERROR)
    dlg.Destroy()

关键步骤:

  1. 创建并显示模态对话框
  2. 检查用户操作(确定/取消)
  3. 验证任务名称非空
  4. 添加到任务列表
  5. 保存数据并刷新界面
  6. 销毁对话框释放资源

编辑任务:

def on_edit_task(self, event):
    idx = self.list_ctrl.GetFirstSelected()
    if idx == -1:
        wx.MessageBox("请先选择要编辑的任务!", "提示", wx.OK | wx.ICON_INFORMATION)
        return
    
    task_idx = self.list_ctrl.GetItemData(idx)
    task = self.tasks[task_idx]
    
    dlg = TaskDialog(self, "编辑任务", task)
    if dlg.ShowModal() == wx.ID_OK:
        task_data = dlg.get_task_data()
        if task_data['name']:
            self.tasks[task_idx] = task_data
            self.save_data()
            self.refresh_list()
        else:
            wx.MessageBox("任务名称不能为空!", "错误", wx.OK | wx.ICON_ERROR)
    dlg.Destroy()

重要技巧:

  • 使用GetItemData()存储和获取任务在原始列表中的索引
  • 这样即使列表经过筛选排序,仍能准确定位任务

删除任务:

def on_delete_task(self, event):
    idx = self.list_ctrl.GetFirstSelected()
    if idx == -1:
        wx.MessageBox("请先选择要删除的任务!", "提示", wx.OK | wx.ICON_INFORMATION)
        return
    
    result = wx.MessageBox("确定要删除选中的任务吗?", "确认删除", 
                          wx.YES_NO | wx.ICON_QUESTION)
    if result == wx.YES:
        task_idx = self.list_ctrl.GetItemData(idx)
        del self.tasks[task_idx]
        self.save_data()
        self.refresh_list()

用户体验优化:

  • 删除前弹出确认对话框
  • 防止误操作造成数据丢失

切换完成状态:

def on_toggle_complete(self, event):
    idx = self.list_ctrl.GetFirstSelected()
    if idx == -1:
        wx.MessageBox("请先选择要操作的任务!", "提示", wx.OK | wx.ICON_INFORMATION)
        return
    
    task_idx = self.list_ctrl.GetItemData(idx)
    self.tasks[task_idx]['completed'] = not self.tasks[task_idx]['completed']
    self.save_data()
    self.refresh_list()
4.4 筛选和搜索功能

核心筛选逻辑:

def refresh_list(self):
    self.list_ctrl.DeleteAllItems()
    
    # 更新分类筛选列表
    self.update_category_filter()
    
    # 获取筛选条件
    search_text = self.search_input.GetValue().lower()
    category = self.category_filter.GetStringSelection()
    priority = self.priority_filter.GetStringSelection()
    status = self.status_filter.GetStringSelection()
    
    filtered_tasks = []
    for i, task in enumerate(self.tasks):
        # 搜索过滤
        if search_text and search_text not in task['name'].lower():
            continue
        
        # 分类过滤
        if category != "全部" and task.get('category', '') != category:
            continue
        
        # 优先级过滤
        if priority != "全部" and task['priority'] != priority:
            continue
        
        # 状态过滤
        if status == "未完成" and task['completed']:
            continue
        if status == "已完成" and not task['completed']:
            continue
        
        filtered_tasks.append((i, task))

筛选器联动:

def update_category_filter(self):
    """动态更新分类筛选下拉列表"""
    current_selection = self.category_filter.GetStringSelection()
    
    # 获取所有不重复的分类
    categories = set()
    for task in self.tasks:
        cat = task.get('category', '').strip()
        if cat:
            categories.add(cat)
    
    # 更新下拉列表
    category_list = ["全部"] + sorted(list(categories))
    self.category_filter.Clear()
    self.category_filter.AppendItems(category_list)
    
    # 恢复之前的选择
    if current_selection in category_list:
        self.category_filter.SetStringSelection(current_selection)
    else:
        self.category_filter.SetSelection(0)

智能化设计:

  • 自动从任务中提取所有分类
  • 去重并排序
  • 保持用户的选择状态
4.5 列表显示和视觉效果

任务显示逻辑:

for original_idx, task in filtered_tasks:
    index = self.list_ctrl.InsertItem(self.list_ctrl.GetItemCount(), 
                                      "✓" if task['completed'] else "")
    self.list_ctrl.SetItem(index, 1, task.get('name', ''))
    self.list_ctrl.SetItem(index, 2, task.get('priority', ''))
    self.list_ctrl.SetItem(index, 3, task.get('category', ''))
    self.list_ctrl.SetItem(index, 4, task.get('due_date') or '')
    created = task.get('created_date', '')
    self.list_ctrl.SetItem(index, 5, created.split()[0] if created else '')
    self.list_ctrl.SetItemData(index, original_idx)
    
    # 设置颜色
    if task['completed']:
        self.list_ctrl.SetItemTextColour(index, wx.Colour(150, 150, 150))
    elif task['priority'] == "高":
        self.list_ctrl.SetItemTextColour(index, wx.Colour(255, 0, 0))
    elif task['priority'] == "中":
        self.list_ctrl.SetItemTextColour(index, wx.Colour(255, 140, 0))

视觉设计:

  • ✓ 标记已完成任务
  • 灰色显示已完成任务(RGB: 150, 150, 150)
  • 红色显示高优先级任务(RGB: 255, 0, 0)
  • 橙色显示中优先级任务(RGB: 255, 140, 0)
  • 默认黑色显示低优先级任务

数据关联技巧:

self.list_ctrl.SetItemData(index, original_idx)
  • 使用SetItemData存储原始索引
  • 即使列表项顺序改变,仍能找到对应的任务数据
4.6 统计信息
def update_stats(self):
    total = len(self.tasks)
    completed = sum(1 for t in self.tasks if t['completed'])
    pending = total - completed
    
    stats = f"总任务: {total}  |  已完成: {completed}  |  待完成: {pending}"
    if total > 0:
        percentage = (completed / total) * 100
        stats += f"  |  完成率: {percentage:.1f}%"
    
    self.stats_text.SetLabel(stats)

统计指标:

  • 总任务数
  • 已完成数量
  • 待完成数量
  • 完成率百分比
4.7 数据持久化

保存数据:

def save_data(self):
    with open(self.data_file, 'w', encoding='utf-8') as f:
        json.dump(self.tasks, f, ensure_ascii=False, indent=2)

关键参数:

  • ensure_ascii=False:支持中文字符
  • indent=2:格式化输出,便于查看和调试

加载数据:

def load_data(self):
    if os.path.exists(self.data_file):
        try:
            with open(self.data_file, 'r', encoding='utf-8') as f:
                self.tasks = json.load(f)
        except:
            self.tasks = []

容错处理:

  • 检查文件是否存在
  • 使用try-except捕获解析异常
  • 异常时初始化为空列表

技术要点总结

1. wxPython事件系统

wxPython采用事件驱动模型:

# 绑定按钮点击事件
add_btn.Bind(wx.EVT_BUTTON, self.on_add_task)

# 绑定列表项双击事件
self.list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_edit_task)

# 绑定键盘事件
self.Bind(wx.EVT_CHAR_HOOK, self.on_key_press)

事件处理函数签名:

def on_event(self, event):
    # 处理逻辑
    pass

2. 布局管理器(Sizer)

wxPython使用Sizer进行布局:

BoxSizer(盒式布局):

vbox = wx.BoxSizer(wx.VERTICAL)    # 垂直布局
hbox = wx.BoxSizer(wx.HORIZONTAL)  # 水平布局

# 添加控件
vbox.Add(widget, proportion=0, flag=wx.EXPAND, border=10)

参数说明:

  • proportion:分配剩余空间的比例(0表示不分配)
  • flag:布局标志(wx.EXPAND, wx.ALIGN_CENTER等)
  • border:边距大小

3. 对话框的使用

模态对话框:

dlg = TaskDialog(self, "添加任务")
if dlg.ShowModal() == wx.ID_OK:
    # 用户点击确定
    data = dlg.get_task_data()
dlg.Destroy()  # 必须销毁对话框

消息框:

wx.MessageBox("提示信息", "标题", wx.OK | wx.ICON_INFORMATION)

4. ListCtrl的使用技巧

插入项目:

index = list_ctrl.InsertItem(row, "第一列内容")
list_ctrl.SetItem(index, 1, "第二列内容")
list_ctrl.SetItem(index, 2, "第三列内容")

存储关联数据:

list_ctrl.SetItemData(index, data_id)
data_id = list_ctrl.GetItemData(index)

获取选中项:

idx = list_ctrl.GetFirstSelected()

设置颜色:

list_ctrl.SetItemTextColour(index, wx.Colour(255, 0, 0))

5. 日期控件处理

wxPython日期与Python日期转换:

# Python date转wxDateTime
wx_date = wx.DateTime()
wx_date.Set(day, month-1, year)  # 注意月份要减1

# wxDateTime转Python格式
date_str = f"{wx_date.GetYear()}-{wx_date.GetMonth()+1:02d}-{wx_date.GetDay():02d}"

性能优化建议

1. 大数据量优化

当任务数量超过1000条时,可以考虑:

# 使用虚拟列表(Virtual ListCtrl)
class VirtualTodoListCtrl(wx.ListCtrl):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, 
                           style=wx.LC_REPORT | wx.LC_VIRTUAL)
        
    def OnGetItemText(self, item, col):
        # 动态返回指定位置的文本
        return self.data[item][col]

2. 延迟刷新

# 批量操作时暂停刷新
self.list_ctrl.Freeze()
# ... 批量操作
self.list_ctrl.Thaw()

3. 数据库存储

对于更大规模的应用,建议使用SQLite:

import sqlite3

def init_database(self):
    conn = sqlite3.connect('tasks.db')
    cursor = conn.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS tasks (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            description TEXT,
            priority TEXT,
            category TEXT,
            due_date TEXT,
            completed INTEGER,
            created_date TEXT
        )
    ''')
    conn.commit()
    conn.close()

功能扩展建议

1. 任务提醒功能

import threading
from datetime import datetime, timedelta

def check_reminders(self):
    while True:
        now = datetime.now()
        for task in self.tasks:
            if task.get('due_date'):
                due = datetime.strptime(task['due_date'], "%Y-%m-%d")
                if 0 <= (due - now).days <= 1:
                    self.show_notification(task)
        time.sleep(3600)  # 每小时检查一次

2. 任务导出功能

def export_to_csv(self):
    import csv
    with open('tasks_export.csv', 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=['name', 'priority', 'category', 'due_date', 'completed'])
        writer.writeheader()
        writer.writerows(self.tasks)

3. 主题切换

class ThemeManager:
    THEMES = {
        'light': {
            'bg': wx.Colour(255, 255, 255),
            'fg': wx.Colour(0, 0, 0),
        },
        'dark': {
            'bg': wx.Colour(30, 30, 30),
            'fg': wx.Colour(200, 200, 200),
        }
    }
    
    def apply_theme(self, panel, theme_name):
        theme = self.THEMES[theme_name]
        panel.SetBackgroundColour(theme['bg'])
        panel.SetForegroundColour(theme['fg'])

4. 任务标签系统

# 为任务添加多个标签
task_data = {
    'name': '项目开发',
    'tags': ['紧急', '重要', '技术'],
    # ... 其他字段
}

# 支持按标签筛选
def filter_by_tags(self, selected_tags):
    return [t for t in self.tasks 
            if any(tag in t.get('tags', []) for tag in selected_tags)]

常见问题解决

1. 中文乱码问题

# 确保所有文件操作使用UTF-8编码
with open(file, 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False)

2. 对话框父窗口问题

# 错误:对话框跟随父窗口最小化
dlg = TaskDialog(self, "添加任务")

# 正确:对话框独立显示
dlg = TaskDialog(None, "添加任务")

3. ListCtrl刷新闪烁

# 使用Freeze/Thaw减少闪烁
self.list_ctrl.Freeze()
self.list_ctrl.DeleteAllItems()
# ... 添加项目
self.list_ctrl.Thaw()

4. 日期格式问题

# 统一使用ISO格式
date_str = datetime.now().strftime("%Y-%m-%d")
date_obj = datetime.strptime(date_str, "%Y-%m-%d")

项目部署

1. 打包成可执行文件

使用PyInstaller:

pip install pyinstaller
pyinstaller --onefile --windowed todolist_app.py

2. 添加图标

pyinstaller --onefile --windowed --icon=app.ico todolist_app.py

3. 配置文件

创建config.ini

[Settings]
data_file = todolist_data.json
window_width = 900
window_height = 650
theme = light

运行结果

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值