Pyqt5相关文章:
快速掌握Pyqt5的三种主窗口
快速掌握Pyqt5的2种弹簧
快速掌握Pyqt5的5种布局
快速弄懂Pyqt5的5种项目视图(Item View)
快速弄懂Pyqt5的4种项目部件(Item Widget)
快速掌握Pyqt5的6种按钮
快速掌握Pyqt5的10种容器(Containers)
快速掌握Pyqt5的20种输入控件(Input Widgets)
快速掌握Pyqt5的9种显示控件
详细学习Pyqt5中的5种布局方式
详细学习Pyqt5中的6种按钮
详细学习Pyqt5中的2种弹簧
详细学习Pyqt5的5种项目视图(Item View)
详细学习Pyqt5的4种项目部件(Item Widget)
详细学习Pyqt5的20种输入控件(Input Widgets)
详细学习Pyqt5的9种显示控件
详细学习Pyqt5的10种容器(Containers)
详细学习PyQt5与数据库交互
详细学习PyQt5中的多线程
快速学习PyQt5的动画和图形效果
快速学习PyQt5的高级自定义控件
快速学会绘制Pyqt5中的所有图(上)
快速学会绘制Pyqt5中的所有图(下)
待续。。。

项目软件最终效果图:

通过“待办事项列表项目”快速学习Pyqt5的一些特性-LMLPHP

第一部分:项目概述

本项目的目标是创建一个简单而直观的待办事项列表应用程序。这个应用程序可以帮助用户有效地管理他们的日常任务,提供以下关键功能:

  1. 添加任务:允许用户输入并添加新任务到待办事项列表中。
  2. 删除任务:提供选项以从列表中删除不再需要的任务。
  3. 编辑任务:让用户能够修改现有任务的描述。
  4. 搜索和过滤:使用户能够通过关键词搜索任务,便于快速找到特定任务。
  5. 进度跟踪:通过进度条展示任务完成的总体进度,帮助用户了解他们完成任务的情况。

应用程序的用户界面(UI)设计注重简洁性和易用性,以确保用户能够轻松地进行日常任务管理。界面包括:

  1. 任务输入框:用于输入新任务的文本区域。
  2. 控制按钮:包括添加、删除和编辑任务的操作按钮。
  3. 任务显示区:展示任务列表,每个任务项包含一个复选框和任务描述。
  4. 搜索框:允许用户输入关键词以过滤任务列表。
  5. 进度指示器:动态显示完成的任务占总任务的百分比。

第二部分:环境搭建

准备PyQt5图形库

  • 打开命令提示符或终端,并运行以下命令:
    pip install PyQt5
    
  • 这个命令会从Python包索引(PyPI)下载并安装PyQt5及其依赖项。

第三部分:基础知识讲解

1. QApplication 对象

每个PyQt5应用程序的核心是QApplication对象。这个对象负责管理GUI应用程序的控制流和主要设置。在任何PyQt5程序中,QApplication对象是必须的。

app = QApplication([])

这段代码创建了一个QApplication实例,它是我们待办事项列表应用程序的起点。

2. QMainWindowQWidget

QMainWindow 是主窗口类,为应用程序提供了一个带有菜单栏、工具栏、状态栏和中心部件的框架。在我们的项目中,TodoList类继承自QMainWindow

class TodoList(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("待办事项列表")
        self.setGeometry(300, 300, 600, 500)
        self.setWindowIcon(QIcon('icon.png'))

QWidget 是所有用户界面对象的基类。在这里,我们创建了一个QWidget作为中心部件,并在其上布置其他控件。

central_widget = QWidget(self)
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
3. 布局管理

布局管理器,如QHBoxLayoutQVBoxLayout,是用来在窗口中组织控件的工具。在我们的项目中,我们使用了QVBoxLayout来垂直排列控件,并使用了QHBoxLayout来水平排列按钮。

layout = QVBoxLayout(central_widget)
buttons_layout = QHBoxLayout()
4. 控件和事件处理

在PyQt5中,控件是用户与应用程序交互的元素。我们的项目使用了多种控件,例如QLineEdit来输入任务,QPushButton来添加、删除和编辑任务。

self.task_input = QLineEdit(self)
add_button = QPushButton("添加任务", self)
delete_button = QPushButton("删除任务", self)

这些按钮通过点击事件触发相应的函数,如add_taskdelete_task

5. 自定义控件 - MovingTextProgressBar

在PyQt5中,我们还可以自定义控件。在此项目中,我们定义了MovingTextProgressBar,这是QProgressBar的一个子类,用来显示任务的完成进度。

class MovingTextProgressBar(QProgressBar):
    def __init__(self, parent=None):
        super().__init__(parent)
    
    def paintEvent(self, event):
        # Custom painting code here

这个自定义进度条在进度变化时显示百分比,并根据进度动态调整文本位置。

6. 数据存储和加载

最后,我们的项目还涉及到数据的保存和加载。使用Python的json模块,我们可以将任务列表保存到一个文件,并在应用程序启动时加载这些任务。

def save_tasks(self):
    tasks = []
    for i in range(self.task_list.count()):
        # 代码来保存任务
    with open('tasks.json', 'w') as file:
        json.dump(tasks, file)

def load_tasks(self):
    try:
        with open('tasks.json', 'r') as file:
            # 代码来加载任务
    except Exception as e:
        print(f"Error loading tasks: {e}")

第四部分:逐步构建项目

1. 构建静态窗口方法(初始化__init__)详解

类定义和初始化
class TodoList(QMainWindow):
    def __init__(self):
        super().__init__()
  • TodoList 类继承自 PyQt5 的 QMainWindow 类,提供了一个主窗口框架。
  • __init__ 方法是类的构造函数,用于初始化这个窗口。
  • super().__init__() 调用基类的构造函数,是创建窗口的基本步骤。
设置窗口属性
self.setWindowTitle("待办事项列表")
self.setGeometry(300, 300, 600, 500)
self.setWindowIcon(QIcon('icon.png'))
  • setWindowTitle 设置窗口的标题。
  • setGeometry 设置窗口的位置和大小。这里窗口被放置在屏幕的 (300, 300) 位置,大小为 600x500 像素。
  • setWindowIcon 设置窗口的图标。图标文件应该是名为 ‘icon.png’ 的图像文件。
创建中心小部件和布局
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
  • 创建了一个QWidget作为窗口的中心小部件。
  • setCentralWidget 将这个小部件设置为主窗口的中心区域。
  • QVBoxLayout 创建了一个垂直布局,用于在中心小部件中垂直排列其他控件。
添加任务输入框
self.task_input = QLineEdit(self)
self.task_input.setMinimumHeight(30)
self.task_input.returnPressed.connect(self.add_task)
layout.addWidget(self.task_input)
  • QLineEdit 创建了一个文本输入框,用于输入任务。
  • setMinimumHeight 设置了输入框的最小高度。
  • returnPressed.connect 将输入框中的回车键事件连接到 add_task 方法;QLineEdit 控件有一个名为 returnPressed 的信号,当用户在该控件中按下回车键时,会触发去调用括号内的方法。
  • addWidget 将输入框添加到布局中。
添加控制按钮
buttons_layout = QHBoxLayout()
add_button = QPushButton("添加任务", self)
add_button.clicked.connect(self.add_task)
buttons_layout.addWidget(add_button)

delete_button = QPushButton("删除任务", self)
delete_button.clicked.connect(self.delete_task)
buttons_layout.addWidget(delete_button)

edit_button = QPushButton("编辑任务", self)
edit_button.clicked.connect(self.edit_task)
buttons_layout.addWidget(edit_button)

layout.addLayout(buttons_layout)
  • QHBoxLayout 创建了一个水平布局。
  • QPushButton 创建了添加、删除和编辑任务的按钮。
  • clicked.connect 将按钮点击事件连接到相应的方法。
  • addWidget 将按钮添加到水平布局中。
  • addLayout 将水平布局添加到主垂直布局中。
添加任务列表、搜索框和进度条
self.task_list = QListWidget(self)
layout.addWidget(self.task_list)

self.search_input = QLineEdit(self)
self.search_input.setPlaceholderText("搜索任务...")
self.search_input.textChanged.connect(self.search_tasks)
layout.addWidget(self.search_input)

self.process_label = QLabel(self)
self.process_label.setText("任务进度:")
layout.addWidget(self.process_label)

self.progress_bar = MovingTextProgressBar(self)
self.progress_bar.setMaximum(100)
layout.addWidget(self.progress_bar)
  • QListWidget 创建了一个任务列表。
  • QLineEdit 用作搜索框。
  • QLabel 显示了进度条的标签。
  • MovingTextProgressBar 是一个自定义的进度条类,用来展示任务完成进度。
加载任务和应用样式表
self.load_tasks()
self.update_progress()
self.apply_stylesheet()
  • load_tasks 加载保存的任务。
  • update_progress 更新进度条的状态。
  • apply_stylesheet 应用自定义样式来美化界面。

2. 自定义控件(自定义进度条MovingTextProgressBar)详解

这段代码定义了一个自定义的进度条控件 MovingTextProgressBar,它继承自PyQt5的 QProgressBar。这个自定义控件重写了进度条的绘制方法,使得进度条中可以显示带有百分比的文本。让我们逐步分析这段代码:

类定义和初始化
class MovingTextProgressBar(QProgressBar):
    def __init__(self, parent=None):
        super().__init__(parent)
  • MovingTextProgressBar 类继承自 QProgressBar,使用 QProgressBar 的所有功能并添加新的特性。
  • __init__ 方法是构造函数,用于初始化这个控件。parent 参数允许这个控件被嵌入到其他QWidget中。

绘制进度条

def paintEvent(self, event):
    painter = QPainter(self)
    # ... 绘制代码 ...
  • paintEvent 方法是Qt中用于绘制控件的事件处理器。
  • QPainter 对象用于所有绘图操作。
绘制背景
rect = self.rect()
painter.setBrush(self.palette().color(QPalette.Window))
painter.drawRect(rect)
  • self.rect() 获取控件的矩形区域。
  • setBrush 设置画笔的填充颜色,这里使用窗口的默认背景色。
  • drawRect 绘制进度条的背景矩形。
计算并绘制进度
progress_rect = QRect(rect)
progress_rect.setWidth(int(rect.width() * self.value() / self.maximum()))
painter.setBrush(self.palette().color(QPalette.Highlight))
painter.drawRect(progress_rect)
  • 计算进度条的宽度,基于进度条的当前值和最大值。
  • 设置画笔为高亮颜色(通常用于进度条的填充颜色)。
  • 绘制表示进度的矩形。
绘制文本
painter.setPen(self.palette().color(QPalette.Text))
text = f"{self.value():.2f}%"
text_rect = QRect(rect)
text_width = painter.fontMetrics().width(text)
# ... 文本位置计算和绘制 ...
  • 设置画笔颜色用于绘制文本。
  • 创建要显示的文本,显示当前进度的百分比。
  • fontMetrics().width(text) 用于计算文本的宽度,以便于定位。
计算文本位置并绘制
if self.value() > 0:
    text_position = max(0, progress_rect.width() - text_width - 5)
else:
    text_position = int((rect.width() - text_width) / 2)
text_rect.setLeft(text_position)
painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, text)
  • 根据进度条的填充情况计算文本的位置。
  • 如果有进度(值大于0),则确保文本位于进度条填充的右侧。
  • 如果没有进度,则将文本居中。
  • drawText 实际绘制文本。

总结

MovingTextProgressBar 类通过重写 paintEvent 方法自定义了进度条的绘制方式。这种自定义控件使得进度条不仅能够显示进度的可视化表示,还能在进度条内部显示具体的进度百分比,增强了用户界面的信息展示能力。这个自定义控件可以被重复使用在任何需要显示带文本的进度条的场景中。

3. 添加任务(add_task) 方法详解

获取任务文本

首先,我们从QLineEditself.task_input)获取用户输入的文本。这是用户希望添加到任务列表中的任务描述。

task_text = self.task_input.text()
if task_text:
    ...

这段代码检查是否有文本输入。如果task_text不为空,那么接下来的代码就会执行,将这个新任务添加到任务列表中。

创建列表项

接下来,我们创建一个QListWidgetItem,这将是任务列表中的一个新项。

item = QListWidgetItem()
item.setSizeHint(QSize(0, 40))

这里,setSizeHint(QSize(0, 40))是为了确保列表项有足够的空间来展示任务,其中40是列表项的高度。

设计任务小部件

现在,我们创建一个小部件来展示任务的文本和一个复选框。这个小部件将被放置在任务列表项中。

task_widget = QWidget()
task_layout = QHBoxLayout()
task_layout.setContentsMargins(5, 0, 5, 0)

我们使用QHBoxLayout(水平布局)来放置复选框和任务标签。setContentsMargins方法用于设置布局边距。

添加复选框和标签

我们接着添加一个QCheckBox和一个QLabel到布局中。复选框用于标记任务是否完成,而标签显示任务的文本。

chkBox = QCheckBox()
chkBox.stateChanged.connect(self.update_progress)

task_label = QLabel(task_text)
task_label.setMargin(5)

task_layout.addWidget(chkBox)
task_layout.addWidget(task_label)

stateChanged信号连接到update_progress方法,这样当复选框的状态改变时,进度条会更新。

设置任务小部件的布局并添加到列表

最后,我们将这个布局应用到task_widget,然后将这个小部件设置为item的小部件。

task_layout.addStretch(1)
task_widget.setLayout(task_layout)

self.task_list.addItem(item)
self.task_list.setItemWidget(item, task_widget)

addStretch(1)确保复选框和标签靠左排列,余下的空间被拉伸填充。

清除输入字段并更新进度

任务添加到列表后,输入框被清空,准备接受下一个任务的输入。同时,我们调用update_progress方法来更新进度条。

self.task_input.clear()
self.update_progress()
总结

add_task方法的核心是创建一个新的列表项,将用户输入的任务文本放入一个包含复选框和标签的小部件中,并将这个小部件添加到任务列表中。这个方法体现了PyQt5在处理用户界面和事件方面的灵活性和强大功能。通过这种方式,我们的待办事项列表应用程序能够动态地响应用户的输入,提供一个直观且互动的用户体验。

4. 删除任务(delete_task) 方法详解

循环遍历所选任务

delete_task方法首先遍历所有被用户选中的任务项。在PyQt5中,可以通过selectedItems()方法获取到QListWidget中所有被用户选中的项。

for item in self.task_list.selectedItems():
    ...

这段代码遍历任务列表中的每个被选中的项(item)。

删除选中的任务

接下来,使用takeItem方法从列表中移除这些选中的项。takeItem需要一个索引参数,我们通过row(item)方法获取这个索引。

self.task_list.takeItem(self.task_list.row(item))

这里,row(item)返回被选中项的索引,然后takeItem根据这个索引删除相应的项。

更新进度条

删除任务后,进度条需要更新以反映当前任务的完成状态。

self.update_progress()

调用update_progress方法来重新计算并更新进度条。

编辑任务(edit_task) 方法详解

检查是否有任务被选中

在编辑任务前,首先检查是否有任务被选中。如果至少有一个任务被选中,我们将取第一个选中的任务进行编辑。

selected_items = self.task_list.selectedItems()
if selected_items:
    item = selected_items[0]
    ...

这段代码确定是否有任务被选中,并将第一个选中的任务项赋值给item变量。

弹出对话框以编辑任务

使用QInputDialog.getText方法弹出一个对话框,允许用户编辑选中任务的文本。这个方法返回用户输入的新文本(new_text)和一个布尔值(ok),表示用户是否点击了对话框的确认按钮。

new_text, ok = QInputDialog.getText(self, "编辑任务", "任务描述:", QLineEdit.Normal, item.text())

这里的item.text()是当前任务的文本,用作对话框中的初始值。

更新任务文本

如果用户点击了确认并输入了新文本,则更新任务的文本。

if ok and new_text:
    item.setText(new_text)

setText方法用于更新列表项的文本为用户输入的新文本。

总结

delete_task方法通过遍历并移除选中的任务项来实现删除功能,而edit_task方法则通过弹出对话框并更新任务文本来实现编辑功能。这两个方法展示了如何在PyQt5应用程序中处理用户交互,如选择、删除和编辑列表项。通过这些方法,我们的待办事项列表应用变得更加灵活和用户友好,使用户能够轻松地管理他们的任务。

5. load_tasks 方法详解

尝试读取任务数据

load_tasks方法首先尝试打开一个名为tasks.json的文件,该文件包含以前保存的任务数据。

try:
    with open('tasks.json', 'r') as file:
        tasks = json.load(file)
    ...
except Exception as e:
    print(f"Error loading tasks: {e}")

这里使用json.load(file)将文件中的JSON数据转换成Python对象。如果文件不存在或者文件格式不正确,会抛出异常,并打印错误信息。

遍历任务并添加到列表

接下来,遍历从文件中加载的每个任务,并将它们添加到任务列表中。

for task in tasks:
    item = QListWidgetItem()
    self.task_list.addItem(item)

    task_widget = QWidget()
    task_layout = QHBoxLayout()
    task_layout.setContentsMargins(5, 5, 5, 5)

    chkBox = QCheckBox()
    chkBox.setChecked(task['completed'])
    chkBox.stateChanged.connect(self.update_progress)

    task_label = QLabel(task['text'])
    task_layout.addWidget(chkBox)
    task_layout.addWidget(task_label)
    task_layout.addStretch(1)

    task_widget.setLayout(task_layout)

    item.setSizeHint(task_widget.sizeHint())
    self.task_list.setItemWidget(item, task_widget)

在这个循环中,每个任务被创建为一个QListWidgetItem,其中包含一个复选框(表示任务是否完成)和一个标签(显示任务文本)。这样做可以确保应用程序在启动时加载用户之前的任务。

6. save_tasks 方法详解

准备保存的任务数据

save_tasks方法中,首先创建一个空列表tasks,用于存放将要保存到文件的任务数据。

tasks = []
遍历任务列表并收集数据

接着,遍历任务列表中的每一项,收集任务的文本和完成状态。

for i in range(self.task_list.count()):
    item = self.task_list.item(i)
    widget = self.task_list.itemWidget(item)
    if widget:
        label = widget.findChild(QLabel)
        checkbox = widget.findChild(QCheckBox)
        tasks.append({'text': label.text(), 'completed': checkbox.isChecked()})

这里使用findChild方法来找到每个任务项中的QLabelQCheckBox,并获取它们的文本和勾选状态。

将任务数据保存到文件

最后,使用json.dump将任务数据以JSON格式保存到tasks.json文件中。

with open('tasks.json', 'w') as file:
    json.dump(tasks, file)

这样,当用户关闭应用程序时,他们的任务数据被保存,并且可以在下次打开应用时重新加载。

总结

load_taskssave_tasks方法是待办事项列表应用程序的关键部分,它们确保了任务数据的持久化。通过这些方法,用户的任务列表在应用程序关闭和重新打开之间保持一致,从而提供了更好的用户体验和数据的持久性。

总结

本文详细介绍了如何使用Python和PyQt5创建一个功能完备的待办事项列表应用程序。从项目概述到环境搭建,再到基础概念的讲解和实际构建过程,文章全面覆盖了应用程序开发的各个方面。通过复现这个项目,可以学习到以下知识:PyQt5的基本使用、事件处理和信号槽机制、自定义控件的创建、数据保存。

附录(全部代码)

import json
from PyQt5.QtWidgets import QLabel,QHBoxLayout,QApplication, QMainWindow, QListWidget, QListWidgetItem, QLineEdit, QPushButton, QVBoxLayout, QWidget, QCheckBox, QProgressBar, QInputDialog, QComboBox
from PyQt5.QtCore import QSize,QRect, Qt
from PyQt5.QtGui import QIcon,QPainter, QColor, QPalette
 

class MovingTextProgressBar(QProgressBar):
    def __init__(self, parent=None):
        super().__init__(parent)

    def paintEvent(self, event):
        painter = QPainter(self)
        # Draw the progress bar's background
        rect = self.rect()
        painter.setBrush(self.palette().color(QPalette.Window))  # Use 'Window' role for background
        painter.drawRect(rect)

        # Calculate the width of the progress bar
        progress_rect = QRect(rect)
        progress_rect.setWidth(int(rect.width() * self.value() / self.maximum()))
        painter.setBrush(self.palette().color(QPalette.Highlight))  # Use 'Highlight' role for progress
        painter.drawRect(progress_rect)

        # Set the text color
        painter.setPen(self.palette().color(QPalette.Text))  # Use 'Text' role for text color

        # Calculate the text position
        text = f"{self.value():.2f}%"
        text_rect = QRect(rect)
        text_width = painter.fontMetrics().width(text)
        # Draw the text
        if self.value() > 0:
            # If there is progress, ensure the text is on the right side of the filled area
            text_position = max(0, progress_rect.width() - text_width - 5)  # Use max to avoid negative position
        else:
            # If there is no progress, center the text
            text_position = int((rect.width() - text_width) / 2)  # Cast to int to ensure correct argument type

        text_rect.setLeft(text_position)
        painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, text)







class TodoList(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("待办事项列表")
        self.setGeometry(300, 300, 600, 500)
        self.setWindowIcon(QIcon('icon.png'))  # 设置窗口图标,确保 'icon.png' 在您的文件夹中  

        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        layout.setSpacing(10)
        layout.setAlignment(Qt.AlignTop)

        self.task_input = QLineEdit(self)
        self.task_input.setMinimumHeight(30)
        self.task_input.returnPressed.connect(self.add_task)  # 连接信号

        layout.addWidget(self.task_input)

        # Create a horizontal layout for the buttons
        buttons_layout = QHBoxLayout()

        # Add Task Button
        add_button = QPushButton("添加任务", self)
        add_button.clicked.connect(self.add_task)
        buttons_layout.addWidget(add_button)

        # Delete Task Button
        delete_button = QPushButton("删除任务", self)
        delete_button.clicked.connect(self.delete_task)
        buttons_layout.addWidget(delete_button)

        # Edit Task Button
        edit_button = QPushButton("编辑任务", self)
        edit_button.clicked.connect(self.edit_task)
        buttons_layout.addWidget(edit_button)

        # Add the horizontal layout to the main vertical layout
        layout.addLayout(buttons_layout)

        self.task_list = QListWidget(self)
        layout.addWidget(self.task_list)



        self.search_input = QLineEdit(self)
        self.search_input.setPlaceholderText("搜索任务...")
        self.search_input.textChanged.connect(self.search_tasks)
        layout.addWidget(self.search_input)



        self.process_label = QLabel(self)
        self.process_label.setText("任务进度:")
        layout.addWidget(self.process_label)

        self.progress_bar = MovingTextProgressBar(self)
        self.progress_bar.setMaximum(100)
        layout.addWidget(self.progress_bar)

        self.load_tasks()
        self.update_progress()  # Update the progress bar


        self.apply_stylesheet()







    def apply_stylesheet(self):
        self.setStyleSheet("""  
            QListWidget, QLineEdit, QProgressBar, QComboBox {                font-size: 14px;                border: 1px solid #c0c0c0;                border-radius: 5px;                padding: 5px;            }            QPushButton {                background-color: #5cacee;                border-radius: 5px;                color: white;                padding: 6px;                margin: 5px 0;            }            QPushButton:hover {                background-color: #1e90ff;            }        """)

    def add_task(self):
        task_text = self.task_input.text()
        if task_text:
            # 创建一个新的QListWidgetItem并设置其大小提示
            item = QListWidgetItem()
            item.setSizeHint(QSize(0, 40))  # 根据需要调整高度

            # 创建包含复选框和标签的小部件
            task_widget = QWidget()
            task_layout = QHBoxLayout()
            task_layout.setContentsMargins(5, 0, 5, 0)  # 适当设置边距

            # 创建复选框并连接状态变化信号
            chkBox = QCheckBox()
            chkBox.stateChanged.connect(self.update_progress)

            # 创建标签显示文本
            task_label = QLabel(task_text)
            task_label.setMargin(5)  # 如果文本靠得太近,可以添加一些边距

            # 将复选框和标签添加到布局
            task_layout.addWidget(chkBox)
            task_layout.addWidget(task_label)
            task_layout.addStretch(1)  # 添加拉伸因子使得控件靠左排列

            # 设置任务小部件的布局
            task_widget.setLayout(task_layout)

            # 将任务小部件设置为列表项的小部件
            self.task_list.addItem(item)
            self.task_list.setItemWidget(item, task_widget)

            # 清除输入字段并更新进度
            self.task_input.clear()
            self.update_progress()

    def delete_task(self):
        for item in self.task_list.selectedItems():
            self.task_list.takeItem(self.task_list.row(item))
        self.update_progress()

    def update_progress(self):
        total_tasks = self.task_list.count()
        completed_tasks = sum(1 for i in range(total_tasks) if
                              self.task_list.itemWidget(self.task_list.item(i)).findChild(QCheckBox).isChecked())
        # Protect against division by zero if there are no tasks
        progress = int((completed_tasks / total_tasks) * 100) if total_tasks > 0 else 0
        self.progress_bar.setValue(progress)
        self.progress_bar.setFormat(f"{progress:.2f}%" if progress >= 0 else "0.00%")

        self.save_tasks()

    def edit_task(self):
        selected_items = self.task_list.selectedItems()
        if selected_items:
            item = selected_items[0]
            new_text, ok = QInputDialog.getText(self, "编辑任务", "任务描述:", QLineEdit.Normal, item.text())
            if ok and new_text:
                item.setText(new_text)

    def closeEvent(self, event):
        tasks = []
        for i in range(self.task_list.count()):
            item = self.task_list.item(i)
            widget = self.task_list.itemWidget(item)
            if widget:
                checkbox = widget.findChild(QCheckBox)
                tasks.append({'text': item.text(), 'completed': checkbox.isChecked()})
        with open('tasks.json', 'w') as file:
            json.dump(tasks, file)
        event.accept()

    def load_tasks(self):
        try:
            with open('tasks.json', 'r') as file:
                tasks = json.load(file)
            for task in tasks:

                item = QListWidgetItem()
                self.task_list.addItem(item)

                task_widget = QWidget()
                task_layout = QHBoxLayout()
                task_layout.setContentsMargins(5, 5, 5, 5)

                chkBox = QCheckBox()
                # First, disconnect any existing connections to avoid duplicate signals
                try:
                    chkBox.stateChanged.disconnect()
                except TypeError:
                    # If there is no connection, a TypeError is thrown, which is fine
                    pass
                # Then, set the checked state
                chkBox.setChecked(task['completed'])
                # Now, connect the signal to the slot
                chkBox.stateChanged.connect(self.update_progress)

                task_label = QLabel(task['text'])
                task_layout.addWidget(chkBox)
                task_layout.addWidget(task_label)
                task_layout.addStretch(1)

                task_widget.setLayout(task_layout)

                item.setSizeHint(task_widget.sizeHint())
                self.task_list.setItemWidget(item, task_widget)

            # After all tasks have been loaded, update the progress bar
                self.update_progress()



        except Exception as e:
            print(f"Error loading tasks: {e}")

            # 新增 sort_tasks 方法




    def search_tasks(self, keyword):
        for i in range(self.task_list.count()):
            item = self.task_list.item(i)
            widget = self.task_list.itemWidget(item)
            if widget:
                label = widget.findChild(QLabel)
                # 确保我们找到了标签并且比较其文本
                if label and keyword.lower() in label.text().lower():
                    item.setHidden(False)
                else:
                    item.setHidden(True)

    # 程序结束时调用
    def closeEvent(self, event):
        self.save_tasks()
        super().closeEvent(event)

    # 新增保存和加载任务的方法
    def save_tasks(self):
        tasks = []
        for i in range(self.task_list.count()):
            item = self.task_list.item(i)
            widget = self.task_list.itemWidget(item)
            if widget:
                label = widget.findChild(QLabel)
                checkbox = widget.findChild(QCheckBox)
                task_text = label.text() if label else ""
                task_completed = checkbox.isChecked() if checkbox else False
                tasks.append({'text': task_text, 'completed': task_completed})
        with open('tasks.json', 'w') as file:
            json.dump(tasks, file)


app = QApplication([])
window = TodoList()
window.show()
app.exec_()
12-17 05:14