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

你可以在这里看到现场演示。

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

本教程包括以下步骤:

  • 开始使用 Flet
  • 添加页面控件和处理事件
  • 查看、编辑和删除列表项
  • 过滤列表项
  • 最后的润色
  • 部署应用程序

Flet 入门

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

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

pip install flet

首先,让我们创建一个简单的 hello-world 应用程序。

hello.py使用以下内容创建:

import flet
from flet import Page, Text

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

flet.app(target=main)

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

添加页面控件和处理事件

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

首先,我们需要一个用于输入任务名称的TextField ,以及一个带有事件处理程序的“+” FloatingActionButton ,它将显示一个带有新任务的复选框。

todo.py使用以下内容创建:

import flet
from flet import Checkbox, FloatingActionButton, Page, TextField, icons

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

运行应用程序,您应该会看到如下页面:

页面布局

现在让我们让应用程序看起来不错!我们希望整个应用程序位于页面的顶部中心,占据 600 像素的宽度。TextField 和“+”按钮应该水平对齐,并占据整个应用程序宽度:

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

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

import flet
from flet import Checkbox, Column, FloatingActionButton, Page, Row, TextField, icons


def main(page: Page):
    def add_clicked(e):
        tasks_view.controls.append(Checkbox(label=new_task.value))
        new_task.value = ""
        view.update()

    new_task = TextField(hint_text="Whats needs to be done?", expand=True)
    tasks_view = Column()
    view = Column(
        width=600,
        controls=[
            Row(
                controls=[
                    new_task,
                    FloatingActionButton(icon=icons.ADD, on_click=add_clicked),
                ],
            ),
            tasks_view,
        ],
    )

    page.horizontal_alignment = "center"
    page.add(view)

flet.app(target=main)

运行应用程序,您应该会看到如下页面:

可重用的 UI组件

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

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

todo.py

import flet
from flet import Checkbox, Column, FloatingActionButton, Page, Row, TextField, UserControl, icons

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

        # application's root control (i.e. "view") containing all other controls
        return Column(
            width=600,
            controls=[
                Row(
                    controls=[
                        self.new_task,
                        FloatingActionButton(icon=icons.ADD, on_click=self.add_clicked),
                    ],
                ),
                self.tasks,
            ],
        )

    def add_clicked(self, e):
        self.tasks.controls.append(Checkbox(label=self.new_task.value))
        self.new_task.value = ""
        self.update()


def main(page: Page):
    page.title = "ToDo App"
    page.horizontal_alignment = "center"
    page.update()

    # create application instance
    todo = TodoApp()

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

flet.app(target=main)

注意 尝试在页面中添加两个TodoApp组件: ```python

create application instance

app1 = TodoApp() app2 = TodoApp()

add application's root control to the page

page.add(app1, app2)

## 查看、编辑和删除列表项

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

![](http://cdn.h3blog.com/20220920101307.png)


每个任务项由两行表示:`display_view`带有复选框、“编辑”和“删除”按钮的`edit_view`行以及带有文本字段和“保存”按钮的行。`viewcolumn` 用作`display_view``edit_view`行的容器。

在这一步之前,代码足够短,可以完全包含在教程中。展望未来,我们将仅强调在一个步骤中引入的更改。

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

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

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

    def build(self):
        self.display_task = Checkbox(value=False, label=self.task_name)
        self.edit_name = TextField(expand=1)

        self.display_view = Row(
            alignment="spaceBetween",
            vertical_alignment="center",
            controls=[
                self.display_task,
                Row(
                    spacing=0,
                    controls=[
                        IconButton(
                            icon=icons.CREATE_OUTLINED,
                            tooltip="Edit To-Do",
                            on_click=self.edit_clicked,
                        ),
                        IconButton(
                            icons.DELETE_OUTLINE,
                            tooltip="Delete To-Do",
                            on_click=self.delete_clicked,
                        ),
                    ],
                ),
            ],
        )

        self.edit_view = Row(
            visible=False,
            alignment="spaceBetween",
            vertical_alignment="center",
            controls=[
                self.edit_name,
                IconButton(
                    icon=icons.DONE_OUTLINE_OUTLINED,
                    icon_color=colors.GREEN,
                    tooltip="Update To-Do",
                    on_click=self.save_clicked,
                ),
            ],
        )
        return 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(UserControl):
    def build(self):
        self.new_task = TextField(hint_text="Whats needs to be done?", expand=True)
        self.tasks = 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()

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

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

然后,我们将方法的引用传递给task_deleteTask 构造函数并在“删除”按钮事件处理程序上调用它:

class Task(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控件用于显示过滤器:

from flet import Tabs, Tab

# ...

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

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

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

为了根据任务的状态显示不同的任务列表,我们可以维护三个列表,分别是“All”、“Active”和“Completed”任务。然而,我们选择了一种更简单的方法,即我们维护相同的列表,并且仅根据任务的状态更改任务的可见性。

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

class TodoApp(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(UserControl):

    # ...

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

class Task(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 = 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)

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

最后的润色

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

从此处复制此步骤的整个代码。下面我们强调了我们为实现页脚所做的更改:

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

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

        self.view = Column(
            width=600,
            controls=[
                Row([Text(value="Todos", style="headlineMedium")], alignment="center"),
                Row(
                    controls=[
                        self.new_task,
                        FloatingActionButton(icon=icons.ADD, on_click=self.add_clicked),
                    ],
                ),
                Column(
                    spacing=25,
                    controls=[
                        self.filter,
                        self.tasks,
                        Row(
                            alignment="spaceBetween",
                            vertical_alignment="center",
                            controls=[
                                self.items_left,
                                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 应用程序作为 Web 应用程序部署到 Fly.io 或 Replit。

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

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

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

我们很乐意倾听您的反馈!请给我们发送电子邮件,加入Discord上的讨论,关注Twitter。