跳到主要内容

使用Python创建Flet应用程序

要编写Flet应用程序,您不需要成为前端大师,但建议您具备基本的Python和面向对象编程知识。

在本指南中,我们将研究Flet应用程序的结构,学习如何使用Flet控件输出数据,请求用户数据并构建基本页面布局。我们还将介绍一些打包和部署选项,以便将完成的应用程序交付给用户。

安装flet模块

Flet要求使用Python 3.8或更高版本。要开始使用Flet,您首先需要安装flet模块:

pip install flet
备注

要升级flet模块,请运行以下命令:

pip install flet --upgrade

要安装Flet预发行版(供高级用户使用),请运行以下命令:

pip install flet --pre
警告

我们建议将预发行版构建安装到虚拟环境中。

Linux

在Linux和WSL上运行Flet应用程序需要安装 GStreamer 库。最有可能的是您的系统已经安装了它们,但是如果在运行Flet应用程序时出现error while loading shared libraries: libgstapp-1.0.so.0: cannot open shared object file: No such file or directory错误,则需要安装GStreamer。

要在Ubuntu/Debian上安装GStreamer,请运行以下命令:

sudo apt-get update
sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio

有关在其他Linux发行版上安装的详细指南,请参见此指南

WSL

Flet应用程序可以在WSL2上运行。如果出现cannot open display错误,请参考此指南进行故障排除。

基本应用程序结构

一个非常简单的Flet应用程序具有以下结构:

import flet as ft

def main(page: ft.Page):
# 在Page上添加/更新控件
pass

ft.app(target=main)
备注

本节有意称为“基本” ,因为后面的指南中,我们将了解更适用于实际应用程序结构的方法,包括可重用控件。

一个典型的Flet程序以调用flet.app()结束,应用程序开始等待新用户会话。函数 main()是Flet应用程序的入口点。对于每个用户会话,它都会在新线程中调用,并将一个Page实例传递给它。在浏览器中运行Flet应用程序时,每个打开的标签页或页面都会启动一个新的用户会话。在作为桌面应用程序运行时,只会创建一个会话。

Page类似于特定用户的“画布”,是用户会话的可视状态。要构建应用程序的用户界面,您可以向页面添加和删除控件,更新其属性。上面的代码示例将为每个用户显示一个空白页面。

默认情况下,Flet应用程序在本机操作系统窗口中启动,这对于开发非常方便。但是,您可以通过修改对flet.app的调用,将其在新的浏览器窗口中打开:

ft.app(target=main, view=ft.AppView.WEB_BROWSER)
信息

在内部,每个Flet应用程序都是一个Web应用程序,即使在本机操作系统窗口中打开应用程序,内置的Web服务器仍然会在后台启动。Flet Web服务器称为“Fletd”,默认情况下它会在随机TCP端口上进行侦听。您可以指定自定义的TCP端口,然后在打开应用程序时将其与桌面视图一起在浏览器中打开:

flet.app(port=8550, target=main)

在浏览器中打开http://localhost:<port>,以查看您的 Flet 应用的 Web 版本。

控件

用户界面由控件(也称为小部件)组成。要使控件对用户可见,必须将它们添加到 Page 中或嵌套在其他控件中。Page 是最顶层的控件。将控件嵌套到彼此中,可以表示为以 Page 为根的树。

控件只是常规的 Python 类。通过使用与其属性匹配的构造函数参数创建控件实例,例如:

t = ft.Text(value="Hello, world!", color="green")

要在页面上显示控件,将其添加到 Page 的 controls 列表中,并调用 page.update() 发送页面更改到浏览器或桌面客户端:

import flet as ft

def main(page: ft.Page):
t = ft.Text(value="Hello, world!", color="green")
page.controls.append(t)
page.update()

ft.app(target=main)
备注

在下面的例子中,我们将只显示 main 函数的内容。

您可以修改控件属性,UI 将在下一个 page.update() 上更新:

t = ft.Text()
page.add(t) # 它是 page.controls.append(t) 的快捷方式,然后是 page.update()

for i in range(10):
t.value = f"Step {i}"
page.update()
time.sleep(1)

一些控件是"容器"控件(如 Page),它们可以包含其他控件。例如,Row 控件允许将其他控件在一行中排列:

page.add(
ft.Row(controls=[
ft.Text("A"),
ft.Text("B"),
ft.Text("C")
])
)

或者 TextFieldElevatedButton

page.add(
ft.Row(controls=[
ft.TextField(label="Your name"),
ft.ElevatedButton(text="Say my name!")
])
)

page.update() 足够智能,只发送自上次调用以来所做的更改,因此您可以将一些新控件添加到页面中,删除其中一些控件,更改其他控件的属性,然后调用 page.update() 进行批量更新,例如:

for i in range(10):
page.controls.append(ft.Text(f"Line {i}"))
if i > 4:
page.controls.pop(0)
page.update()
time.sleep(0.3)

像按钮这样的一些控件可以具有对用户输入做出反应的事件处理程序,例如 ElevatedButton.on_click

def button_clicked(e):
page.add(ft.Text("Clicked!"))

page.add(ft.ElevatedButton(text="Click me", on_click=button_clicked))

以及一个用于简单 To-Do 的更高级示例:

import flet as ft

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

new_task = ft.TextField(hint_text="Whats needs to be done?", width=300)
page.add(ft.Row([new_task, ft.ElevatedButton("Add", on_click=add_clicked)]))

ft.app(target=main)
信息

Flet 实现了命令式的 UI 模型,其中您需要手动构建具有有状态控件的应用程序 UI,并在更新控件属性时对其进行更改。Flutter 实现了声明式模型,其中 UI 在应用程序数据更改时自动重新构建。 在现代前端应用程序中管理应用程序状态是一个本质上复杂的任务,Flet 的"老派"方法对于没有前端经验的程序员可能更具吸引力。

visible 属性

每个控件都有一个 visible 属性,默认值为 true - 控件在页面上呈现。将 visible 设置为 false 将完全阻止控件(及其任何子控件)在页面画布上进行渲染。隐藏的控件不可通过键盘或鼠标进行聚焦或选择,也不会触发任何事件。

disabled 属性

每个控件都有一个 disabled 属性,默认值为 false - 控件及其所有子控件均为启用状态。 禁用(disabled)属性通常与数据输入控件(如文本框(TextField)、下拉框(Dropdown)、复选框(Checkbox)和按钮)一起使用。 然而,禁用属性可以设置给父控件,并且其值将递归地传播到所有子控件。

例如,如果您有一个包含多个输入控件的表单,您可以分别为每个控件设置禁用属性:

first_name = ft.TextField()
last_name = ft.TextField()
first_name.disabled = True
last_name.disabled = True
page.add(first_name, last_name)

或者,您可以将表单控件放入容器(如Column),然后为该列设置禁用属性:

first_name = ft.TextField()
last_name = ft.TextField()
c = ft.Column(controls=[
first_name,
last_name
])
c.disabled = True
page.add(c)

控件引用

Flet 控件是对象,要访问它们的属性,我们需要保留对这些对象的引用(变量)。

考虑以下示例:

import flet as ft

def main(page):

first_name = ft.TextField(label="First name", autofocus=True)
last_name = ft.TextField(label="Last name")
greetings = ft.Column()

def btn_click(e):
greetings.controls.append(ft.Text(f"Hello, {first_name.value} {last_name.value}!"))
first_name.value = ""
last_name.value = ""
page.update()
first_name.focus()

page.add(
first_name,
last_name,
ft.ElevatedButton("Say hello!", on_click=btn_click),
greetings,
)

ft.app(target=main)

main() 方法开始的位置,我们创建了三个控件,它们将在按钮的 on_click 处理程序中使用:一个用于名字的文本框,一个用于姓氏的文本框,以及一个用于问候消息的容器列。我们创建了带有所有属性设置的控件,并在 main() 方法的末尾的 page.add() 调用中使用它们的引用(变量)。

当添加越来越多的控件和事件处理程序时,将所有控件定义放在一个地方变得困难,因此它们会分散在 main() 体中。浏览 page.add() 参数时,很难想象(在没有在 IDE 中不断跳转到变量定义的情况下)最终的表单会是什么样子:

    page.add(
first_name,
last_name,
ft.ElevatedButton("Say hello!", on_click=btn_click),
greetings,
)

first_name 是一个文本框吗?它是否设置了自动获得焦点属性?greetings 是一个行(Row)还是列(Column)?

Flet 提供了 Ref 实用类,它允许定义对控件的引用,在事件处理程序中使用该引用,并在构建树时将引用设置为实际控件。这个想法来自于 React

定义一个新的类型化控件引用:

first_name = ft.Ref[ft.TextField]()

要访问引用的控件(解引用控件),请使用 Ref.current 属性:

# 清空名字
first_name.current.value = ""

要将控件分配给引用,请将 Control.ref 属性设置为引用:

page.add(
ft.TextField(ref=first_name, label="First name", autofocus=True)
)
备注

所有 Flet 控件都有 ref 属性。

我们可以重写我们的程序来使用引用:

import flet as ft


def main(page):
# 创建引用
first_name = ft.Ref[ft.TextField]()
last_name = ft.Ref[ft.TextField]()
greetings = ft.Ref[ft.Column]()

def btn_click(e):
# 将问候语添加到greetings的controls列表中
greetings.current.controls.append(
ft.Text(f"Hello, {first_name.current.value} {last_name.current.value}!")
)
# 清空输入框的值
first_name.current.value = ""
last_name.current.value = ""
# 页面更新
page.update()
# 重新聚焦到first_name输入框
first_name.current.focus()

# 将控件添加到页面中
page.add(
ft.TextField(ref=first_name, label="First name", autofocus=True),
ft.TextField(ref=last_name, label="Last name"),
ft.ElevatedButton("Say hello!", on_click=btn_click),
ft.Column(ref=greetings),
)

# 启动应用
ft.app(target=main)

现在我们可以清楚地在 page.add() 方法中看到页面的结构和所有控件。

是的,现在的逻辑看起来要多一些,因为需要添加 .current. 来访问引用的控件,但这是个人偏好的问题 :)