跳到主要内容

使用Flet在Python中创建To-Do应用

在本教程中,我们将逐步向您展示如何使用Flet框架在Python中创建一个ToDo Web应用,并将其分享到互联网上。该应用程序是一个只有180行(包括格式化)Python代码的单文件控制台程序,但它是一个支持多用户、具有丰富、响应式用户界面的现代单页应用程序:

您可以在此处查看实时演示。

我们选择ToDo应用程序作为演示,因为它涵盖了创建任何Web应用程序所需的所有基本概念:构建页面布局、添加控件、处理事件、显示和编辑列表、制作可重用的UI组件以及部署选项。

教程分为以下几个步骤:

使用Flet入门

要编写一个Flet Web应用程序,您不需要了解HTML、CSS或JavaScript,但需要基本的Python和面向对象编程知识。

Flet需要Python 3.8或更高版本。要在Python中使用Flet创建Web应用程序,首先需要安装flet模块:

pip install flet

首先,让我们创建一个简单的Hello World应用程序。

创建一个名为hello.py的文件,内容如下:

import flet as ft

def main(page: ft.Page):
page.add(ft.Text(value="Hello, world!"))

ft.app(target=main)

运行这个应用程序,您将看到一个带有问候语的新窗口:

添加页面控件和处理事件

现在我们准备创建一个多用户ToDo应用程序。

首先,我们需要一个TextField用于输入任务名称,以及一个带有事件处理程序的"+" FloatingActionButton,用于显示一个带有新任务的Checkbox

创建一个名为todo.py的文件,内容如下:

import flet as ft

def main(page: ft.Page):
def add_clicked(e):
page.add(ft.Checkbox(label=new_task.value))
new_task.value = ""
page.update()

new_task = ft.TextField(hint_text="What needs to be done?")

page.add(new_task, ft.FloatingActionButton(icon=ft.icons.ADD, on_click=add_clicked))

ft.app(target=main)

运行应用程序,您应该会看到一个类似于以下页面:

页面布局

现在让我们让应用程序看起来漂亮一些!我们希望整个应用程序位于页面的顶部中心,占据600像素的宽度。文本字段和"+"按钮应水平对齐,并占满整个应用程序的宽度:

Row 是一个控件,用于在页面上水平布置其子控件。Column 是一个控件,用于在页面上垂直布置其子控件。

todo.py的内容替换为以下内容:

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="Whats 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.app(target=main)

运行应用程序,您应该看到一个类似于这样的页面:

可重用的UI组件

虽然我们可以继续在main函数中编写我们的应用程序,但最佳实践是创建一个可重用的UI组件。想象一下,您正在开发一个应用程序的标题、侧边菜单或将成为一个更大项目一部分的UI。即使您现在无法想到这样的用途,我们仍建议以组合性和可重用性为目标创建所有的Web应用程序。

为了创建一个可重用的ToDo应用程序组件,我们将把其状态和展示逻辑封装在一个单独的类中:

import flet as ft

class TodoApp(ft.UserControl):
def build(self):
self.new_task = ft.TextField(hint_text="Whats needs to be done?", expand=True)
self.tasks = ft.Column()

# application's root control (i.e. "view") containing all other controls
return ft.Column(
width=600,
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):
self.tasks.controls.append(ft.Checkbox(label=self.new_task.value))
self.new_task.value = ""
self.update()


def main(page: ft.Page):
page.title = "ToDo App"
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.update()

# create application instance
todo = TodoApp()

# add application's root control to the page
page.add(todo)

ft.app(target=main)

在上一步中,我们创建了一个基本的ToDo应用程序,其中任务项显示为复选框。现在让我们通过在任务名称旁边添加"编辑"和"删除"按钮来改进应用程序。"编辑"按钮将切换任务项到编辑模式。

每个任务项由两行表示:display_view行包含复选框、"编辑"和"删除"按钮,edit_view行包含文本字段和"保存"按钮。view列用作display_viewedit_view行的容器。

在此步骤之前,代码足够简短,可以完全包含在教程中。从现在开始,我们将只突出显示引入的更改。

这里复制此步骤的完整代码。下面我们将解释我们所做的更改来实现查看、编辑和删除任务。

为了封装任务项的视图和操作,我们引入了一个新的Task类:

class Task(ft.UserControl):
def __init__(self, task_name):
super().__init__()
self.task_name = task_name

def build(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,
),
],
)
return ft.Column(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()

此外,当点击“添加”按钮时,我们将TodoApp类更改为创建和保存Task实例:

class TodoApp(ft.UserControl):
def build(self):
self.new_task = ft.TextField(hint_text="Whats needs to be done?", expand=True)
self.tasks = ft.Column()
# ...

def add_clicked(self, e):
task = Task(self.new_task.value, self.task_delete)
self.tasks.controls.append(task)
self.new_task.value = ""
self.update()

对于"删除"任务操作,我们在TodoApp类中实现了task_delete()方法,该方法接受任务控制实例作为参数:

class TodoApp(ft.UserControl):
# ...
def task_delete(self, task):
self.tasks.controls.remove(task)
self.update()

然后,我们将task_delete方法的引用传递给了Task构造函数,并在“Delete”按钮的事件处理程序中调用了它:

class Task(ft.UserControl):
def __init__(self, task_name, task_delete):
super().__init__()
self.task_name = task_name
self.task_delete = task_delete

# ...

def delete_clicked(self, e):
self.task_delete(self)

运行应用程序并尝试编辑和删除任务:

过滤任务列表

我们已经拥有了一个功能完善的ToDo应用程序,可以创建、编辑和删除任务。为了更加高效,我们希望能够按照任务的状态来过滤任务。

这里复制本步骤的整个代码。下面我们将解释我们所做的更改来实现过滤功能。

使用Tabs控件来显示过滤器:


# ...

class TodoApp(ft.UserControl):
def __init__(self):
self.tasks = []
self.new_task = ft.TextField(hint_text="Whats needs to be done?", expand=True)
self.tasks = ft.Column()

self.filter = ft.Tabs(
selected_index=0,
on_change=self.tabs_changed,
tabs=[ft.Tab(text="all"), ft.Tab(text="active"), ft.Tab(text="completed")],
)

self.view = ft.Column(
width=600,
controls=[
ft.Row(
controls=[
self.new_task,
ft.FloatingActionButton(icon=ft.icons.ADD, on_click=self.add_clicked),
],
),
ft.Column(
spacing=25,
controls=[
self.filter,
self.tasks,
],
),
],
)

为了根据任务的状态显示不同的任务列表,我们可以维护三个列表,分别是"全部"、"进行中"和"已完成"的任务。然而,我们选择了一种更简单的方法,即只维护一个列表,根据任务的状态来改变任务的可见性。

TodoApp类中,我们重写了update()方法,该方法遍历所有任务,并根据任务的状态更新它们的visible属性:

class TodoApp(ft.UserControl):

# ...

def 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)
)
super().update()

当我们点击选项卡或更改任务状态时,应该进行筛选。当选中选项卡的值或点击任务项复选框时,将调用TodoApp.update()方法。

class TodoApp(ft.UserControl):

# ...

def tabs_changed(self, e):
self.update()

class Task(ft.UserControl):
def __init__(self, task_name, task_status_change, task_delete):
super().__init__()
self.completed = False
self.task_name = task_name
self.task_status_change = task_status_change
self.task_delete = task_delete

def build(self):
self.display_task = ft.Checkbox(
value=False, label=self.task_name, on_change=self.status_changed
)
# ...

def status_changed(self, e):
self.completed = self.display_task.value
self.task_status_change(self)

运行应用程序并尝试通过单击选项卡来筛选任务:

最后的修饰

我们的待办事项应用程序现在几乎完成了。作为最后的修饰,我们将添加一个页脚(Column控件),显示未完成任务的数量(Text控件)和一个“清除已完成”按钮。

这里复制此步骤的全部代码。下面我们突出显示了我们所做的更改来实现页脚:

class TodoApp():
def __init__(self):
# ...

self.items_left = ft.Text("0 items left")

self.view = ft.Column(
width=600,
controls=[
ft.Row([ ft.Text(value="Todos", style="headlineMedium")], alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
controls=[
self.new_task,
ft.FloatingActionButton(icon=ft.icons.ADD, on_click=self.add_clicked),
],
),
ft.Column(
spacing=25,
controls=[
self.filter,
self.tasks,
ft.Row(
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
vertical_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
self.items_left,
ft.OutlinedButton(
text="Clear completed", on_click=self.clear_clicked
),
],
),
],
),
],
)

# ...

def clear_clicked(self, e):
for task in self.tasks.controls[:]:
if task.completed:
self.task_delete(task)

def update(self):
status = self.filter.tabs[self.filter.selected_index].text
count = 0
for task in self.tasks.controls:
task.visible = (
status == "all"
or (status == "active" and task.completed == False)
or (status == "completed" and task.completed)
)
if not task.completed:
count += 1
self.items_left.value = f"{count} active item(s) left"
super().update()

运行应用程序:

部署应用程序

恭喜!您已经使用Flet创建了您的第一个Python应用程序,并且看起来很棒!

现在是时候与世界分享您的应用程序了!

按照这些说明将您的Flet应用程序部署为Fly.io或Replit的Web应用程序。

总结

在本教程中,您学会了如何:

  • 创建一个简单的Flet应用程序;
  • 使用可重用的UI组件;
  • 使用ColumnRow控件设计UI布局;
  • 处理列表:查看、编辑和删除项目,进行过滤;
  • 将您的Flet应用程序部署到Web;

如需进一步阅读,您可以探索控件示例存储库