主题
To-Do 待办事项应用教程
在本教程中,我们将逐步向你展示如何使用 Flet 框架在 Python 中创建一个 To-Do(待办事项)应用,然后将其发布为桌面、移动或网页应用。虽然该应用只是一个仅有 164 行(格式化后!)Python 代码的单文件程序,但它却是一个具有丰富、响应式 UI 的多平台应用程序:

你可以在这里查看在线演示。
我们之所以选择 To-Do 应用作为教程,是因为它涵盖了创建 Flet 应用所需的所有基本概念:构建页面布局、添加控件、处理事件、显示和编辑列表、制作可重用的 UI 组件以及发布选项。
本教程包含以下步骤:
Flet 入门
要使用 Flet 在 Python 中创建多平台应用,你不需要了解 HTML、CSS 或 JavaScript,但确实需要掌握 Python 的基础知识 and 面向对象编程。
在创建你的第一个 Flet 应用之前,你需要配置开发环境,这需要 Python 3.10 或更高版本以及 flet 包。
安装好 Flet 后,让我们创建一个简单的 Hello World 应用。
创建 hello.py 文件并写入以下内容:
hello.py
python
import flet as ft
def main(page: ft.Page):
page.add(ft.Text(value="Hello, world!"))
ft.run(main)运行该应用,你将看到一个包含问候语的新窗口:

添加页面控件与事件处理
首先,我们需要一个 TextField 控件用于输入任务名称,以及一个带事件处理器的 "+" FloatingActionButton 按钮,用于添加并显示一个带有新任务的 Checkbox 控件。
创建 todo.py 并写入以下内容:
todo.py
python
import flet
from flet import Checkbox, FloatingActionButton, Icons, Page, TextField
def main(page: Page):
def add_clicked(e):
page.add(Checkbox(label=new_task.value))
new_task.value = ""
page.update()
new_task = TextField(hint_text="Whats needs to be done?")
page.add(new_task, FloatingActionButton(icon=Icons.ADD, on_click=add_clicked))
flet.run(main)运行应用,你应该会看到类似下面的页面:

页面布局
现在让我们把应用变得美观!我们希望整个应用位于页面顶部的中央,宽度为 600 像素。TextField 和 "+" 按钮应该水平对齐,并占用整个应用的宽度:
Row 控件用于在页面上将其子控件水平排列。Column 控件用于将其子控件垂直排列。
将 todo.py 的内容替换为以下代码:
todo.py
python
import flet as ft
def main(page: ft.Page):
def add_clicked(e):
tasks_view.controls.append(ft.Checkbox(label=new_task.value))
new_task.value = ""
view.update()
new_task = ft.TextField(hint_text="What needs to be done?", expand=True)
tasks_view = ft.Column()
view = ft.Column(
width=600,
controls=[
ft.Row(
controls=[
new_task,
ft.FloatingActionButton(icon=ft.Icons.ADD, on_click=add_clicked),
],
),
tasks_view,
],
)
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(view)
ft.run(main)运行应用,你应该会看到类似下面的页面:

可重用的 UI 组件
虽然我们可以继续在 main 函数中编写应用,但最佳实践是创建一个可重用的 UI 组件(Reusable UI Component)。想象一下,你正在开发一个应用头部、一个侧边栏菜单,或者是一个将作为更大项目一部分的 UI。即使你现在还没有想到这样的用途,我们仍然建议你在创建所有 Flet 应用时都要考虑到组合性和可重用性。
为了制作一个可重用的 To-Do 应用组件,我们将把它的状态和展示逻辑封装在一个独立的类中:
todo.py
python
import flet as ft
@ft.control
class TodoApp(ft.Column):
# 应用的根控件是一个包含所有其他控件的 Column 控件
def init(self):
self.new_task = ft.TextField(hint_text="What needs to be done?", expand=True)
self.tasks_view = ft.Column()
self.width = 600
self.controls = [
ft.Row(
controls=[
self.new_task,
ft.FloatingActionButton(
icon=ft.Icons.ADD, on_click=self.add_clicked
),
],
),
self.tasks_view,
]
def add_clicked(self, e):
self.tasks_view.controls.append(ft.Checkbox(label=self.new_task.value))
self.new_task.value = ""
self.update()
def main(page: ft.Page):
page.title = "To-Do App"
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.update()
# 创建应用实例
todo = TodoApp()
# 将应用的根控件添加到页面中
page.add(todo)
ft.run(main)快来尝试一下吧!
查看、编辑和删除列表项
在上一步中,我们创建了一个基本的 To-Do 应用,任务项显示为复选框。接下来,让我们通过在任务名称旁边添加 “编辑”(Edit)和 “删除”(Delete)按钮来改进这个应用。点击 “编辑” 按钮将使该任务项切换到编辑模式。
每个任务项由两行组成:展示视图行 display_view(包含 Checkbox、“编辑” 和 “删除” 按钮)和编辑视图行 edit_view(包含 TextField 和 “保存” 按钮)。Column 控件作为 display_view 和 edit_view 两行的容器。
为了封装任务项的视图和操作,我们引入了一个新的 Task 类。
此外,我们修改了 TodoApp 类,使其在点击 “添加” 按钮时创建并持有 Task 实例。
对于 “删除” 任务的操作,我们在 TodoApp 类中实现了 task_delete() 方法,该方法接收一个任务控件实例作为参数。
然后,我们将 task_delete 方法的引用传递到 Task 类的构造函数中,并在 “删除” 按钮的事件处理器中调用它。
todo.py
python
from dataclasses import field
from typing import Callable
import flet as ft
@ft.control
class Task(ft.Column):
task_name: str = ""
on_task_delete: Callable[["Task"], None] = field(default=lambda task: None)
def init(self):
self.display_task = ft.Checkbox(value=False, label=self.task_name)
self.edit_name = ft.TextField(expand=1)
self.display_view = ft.Row(
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
vertical_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
self.display_task,
ft.Row(
spacing=0,
controls=[
ft.IconButton(
icon=ft.Icons.CREATE_OUTLINED,
tooltip="Edit To-Do",
on_click=self.edit_clicked,
),
ft.IconButton(
ft.Icons.DELETE_OUTLINE,
tooltip="Delete To-Do",
on_click=self.delete_clicked,
),
],
),
],
)
self.edit_view = ft.Row(
visible=False,
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
vertical_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
self.edit_name,
ft.IconButton(
icon=ft.Icons.DONE_OUTLINE_OUTLINED,
icon_color=ft.Colors.GREEN,
tooltip="Update To-Do",
on_click=self.save_clicked,
),
],
)
self.controls = [self.display_view, self.edit_view]
def edit_clicked(self, e):
self.edit_name.value = self.display_task.label
self.display_view.visible = False
self.edit_view.visible = True
self.update()
def save_clicked(self, e):
self.display_task.label = self.edit_name.value
self.display_view.visible = True
self.edit_view.visible = False
self.update()
def delete_clicked(self, e):
self.on_task_delete(self)
@ft.control
class TodoApp(ft.Column):
# 应用的根控件是一个包含所有其他控件的 Column 控件
def init(self):
self.new_task = ft.TextField(hint_text="What needs to be done?", expand=True)
self.tasks = ft.Column()
self.width = 600
self.controls = [
ft.Row(
controls=[
self.new_task,
ft.FloatingActionButton(
icon=ft.Icons.ADD, on_click=self.add_clicked
),
],
),
self.tasks,
]
def add_clicked(self, e):
task = Task(task_name=self.new_task.value, on_task_delete=self.task_delete)
self.tasks.controls.append(task)
self.new_task.value = ""
self.update()
def task_delete(self, task):
self.tasks.controls.remove(task)
self.update()
def main(page: ft.Page):
page.title = "To-Do App"
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.update()
# 创建应用实例
app = TodoApp()
# 将应用的根控件添加到页面中
page.add(app)
if __name__ == "__main__":
ft.run(main)运行应用,并尝试编辑和删除任务:

筛选列表项
我们现在已经拥有了一个功能完善的 To-Do 应用,可以创建、编辑和删除任务。为了提高效率,我们希望能够根据任务的状态来筛选任务。
你可以从这里复制本步骤的完整代码。下面我们将解释为实现筛选而进行的更改。
我们使用 Tabs 控件来显示筛选器选项:
todo.py
python
# ...
class TodoApp(ft.Column):
# 应用的根控件是一个包含所有其他控件的 Column 控件
def init(self):
self.new_task = ft.TextField(hint_text="Whats needs to be done?", expand=True)
self.tasks = ft.Column()
self.filter = ft.TabBar(
scrollable=False,
tabs=[
ft.Tab(label="all"),
ft.Tab(label="active"),
ft.Tab(label="completed"),
],
)
self.filter_tabs = ft.Tabs(
length=3,
selected_index=0,
on_change=lambda e: self.update(),
content=self.filter,
)
# ...为了根据任务状态显示不同的任务列表,我们可以维护三个不同的列表,分别包含 “全部”、“活动中” 和 “已完成” 任务。然而,我们选择了一种更简单的方法:我们只维护同一个列表,并仅根据任务的状态来改变它的 visibility 属性。
在 TodoApp 类中,我们重写了每次更新控件时都会被调用的 before_update() 方法。它会遍历所有任务,并根据任务状态更新其 visible 属性:
todo.py
python
class TodoApp(ft.Column):
# ...
def before_update(self):
status = self.filter.tabs[self.filter.selected_index].text
for task in self.tasks.controls:
task.visible = (
status == "all"
or (status == "active" and task.completed == False)
or (status == "completed" and task.completed)
)当我们点击标签页(Tab)或改变某个任务状态时,应该触发筛选。在选项卡选定的值发生改变,或点击任务项的复选框时,都会调用 TodoApp.before_update() 方法:
todo.py
python
class TodoApp(ft.Column):
# ...
def tabs_changed(self, e):
self.update()
def task_status_change(self, e):
self.update()
def add_clicked(self, e):
task = Task(
task_name=self.new_task.value,
on_status_change=self.task_status_change,
on_delete=self.task_delete,
)
self.tasks.controls.append(task)
self.new_task.value = ""
self.update()
# ...
class Task(ft.Column):
task_name: str = ""
on_status_change: Callable[[], None] = field(default=lambda: None)
on_delete: Callable[["Task"], None] = field(default=lambda task: None)
# ...
def status_changed(self, e):
self.completed = self.display_task.value
self.task_status_change()运行应用,尝试点击各标签页来筛选任务:

最终完善
我们的 Todo 应用现在几乎完成了。作为最后的完善,我们将添加一个页脚(页脚由 Column 控件组成),用来显示未完成任务的数量(Text 控件)以及一个 “清除已完成任务”(Clear completed)按钮。
完整代码
你可以在这里找到并运行完整代码。

发布应用
恭喜!你已经使用 Flet 创建了你的第一个 Python 应用,它看起来棒极了!
现在是时候与世界分享你的应用了!
按照这些说明将你的 Flet 应用发布为移动、桌面或网页应用。
总结
在本教程中,你学习了如何:
- 创建一个简单的 Flet 应用;
- 使用可重用的 UI 组件;
- 使用 Column 和 Row 控件设计 UI 布局;
- 操作列表:查看、编辑、删除以及筛选项目;
- 将你的 Flet 应用发布到多个平台;