Skip to content

自定义控件 (Custom Controls) ​


虽然 Flet 提供了 140 多个开箱即用的内置控件,但使用 Flet 进行编程的真正魅力在于,你可以利用 Python 的面向对象编程概念,将这些内置控件作为砖瓦,创建出属于你自己的、可复用的自定义 UI 组件。

你可以在 Python 中通过**修改样式(Styling)和/或进行组合(Combining)**现有的 Flet 控件来创建自定义控件。


样式化控件 (Styled controls) ​

最简单的自定义控件是样式化控件。例如,一个在应用中需要多次复用的、具有特定颜色和行为的按钮。

要创建样式化控件,你需要创建一个继承自需要自定义的 Flet 控件的 Python 数据类(Dataclass),本例中我们继承 Button 控件:

python
from dataclasses import field

@ft.control
class MyButton(ft.Button):
    bgcolor: ft.Colors = ft.Colors.ORANGE_300
    color: ft.Colors = ft.Colors.GREEN_800
    style: ft.ButtonStyle = field(
        default_factory=lambda: ft.ButtonStyle(
            shape=ft.RoundedRectangleBorder(radius=10)
        )
    )
    expand: int = 1

你也可以在继承的类上定义 @dataclass 装饰器,其与 @ft.control 效果完全相同:

python
from dataclasses import dataclass, field

@dataclass
class MyButton(ft.Button):
    bgcolor: ft.Colors = ft.Colors.ORANGE_300
    color: ft.Colors = ft.Colors.GREEN_800
    style: ft.ButtonStyle = field(
        default_factory=lambda: ft.ButtonStyle(
            shape=ft.RoundedRectangleBorder(radius=10)
        )
    )
    expand: int = 1

你还可以选择直接在 init() 方法中设置属性值:

python
@ft.control
class MyButton(ft.Button):
    def init(self):
        self.bgcolor = ft.Colors.ORANGE_300
        self.color = ft.Colors.GREEN_800
        self.style = ft.ButtonStyle(
            shape=ft.RoundedRectangleBorder(radius=10)
        )
        self.expand = 1
规则 (Rules) ​
  1. 你应当在继承类上定义 @dataclass 或 @ft.control 装饰器 —— 两者的运行行为一致。
  2. 属性字段若要生效,必须提供类型注解。因此,expand: int = 1 会覆盖继承的父类属性,但 expand = 1 则不会。如果不确定使用什么类型,可以使用 Any(例如 expand: Any = 1)。
  3. 对于简单的数据类型(如 int、bool、str 等),使用直接赋值作为默认值;对于不可变或复杂类型(如类、列表、字典等),请使用 field(default_factory=lambda: <值>)。
  4. 你可以在 init() 方法中设置初始属性值,但是如果在实例化控件时显式传入了参数,例如 MyButton(expand=False),则可能无法通过该方法对其进行覆盖。

现在,你可以在应用中自由地使用全新的自定义按钮控件了:

python
import flet as ft

@ft.control
class MyButton(ft.Button):
    bgcolor: ft.Colors = ft.Colors.ORANGE_300
    color: ft.Colors = ft.Colors.GREEN_800

def main(page: ft.Page):
    def ok_clicked(e):
        print("OK clicked")

    def cancel_clicked(e):
        print("Cancel clicked")

    page.add(
        MyButton(content="OK", on_click=ok_clicked),
        MyButton(content="Cancel", on_click=cancel_clicked),
    )

ft.run(main)

样式化控件示例

你可以前往计算器教程查看使用样式化控件的完整教学实践。


组合控件 (Composite controls) ​

组合式自定义控件通常继承自容器类控件(如 Column、Row、Stack 甚至 View),用来将多个不同的 Flet 控件合并组合。以下是在待办事项(To-Do)应用中可以使用的自定义 Task(任务)控件:

python
import flet as ft

@ft.control
class Task(ft.Row):
    text: str = ""

    def init(self):
        self.text_view = ft.Text(value=self.text)
        self.text_edit = ft.TextField(value=self.text, visible=False)
        self.edit_button = ft.IconButton(icon=ft.Icons.EDIT, on_click=self.edit)
        self.save_button = ft.IconButton(
            visible=False, icon=ft.Icons.SAVE, on_click=self.save
        )
        self.controls = [
            ft.Checkbox(),
            self.text_view,
            self.text_edit,
            self.edit_button,
            self.save_button,
        ]

    def edit(self, e):
        self.edit_button.visible = False
        self.save_button.visible = True
        self.text_view.visible = False
        self.text_edit.visible = True
        self.update()

    def save(self, e):
        self.edit_button.visible = True
        self.save_button.visible = False
        self.text_view.visible = True
        self.text_edit.visible = False
        self.text_view.value = self.text_edit.value
        self.update()

def main(page: ft.Page):
    page.add(
        Task(text="Do laundry"),
        Task(text="Cook dinner"),
    )

ft.run(main)

组合控件示例


生命周期方法 (Life-cycle methods) ​

自定义控件提供了生命周期“钩子”方法,你可以在不同的场景中重写它们以控制控件的生命周期行为。

build() ​

当控件被创建并成功关联其所在的 self.page 时,会触发调用 build() 方法。

如果你的业务逻辑需要在控件构造函数执行时无法获取的环境数据(例如,需要通过访问 self.page 才能判断的属性),请重写 build()。典型示例:在平台自适应应用中,根据 self.page.platform 来为不同平台返回对应的合适图标。

did_mount() ​

在控件被成功渲染添加至页面中并分配了其在底层的 transient uid 后调用。

如果需要在控件挂载上线后立即启动异步服务(例如,需要定时向外部 OpenWeather API 请求天气状态并在此控件上实时自我渲染的 Weather widget 气象组件),请重写 did_mount() 方法。

will_unmount() ​

在控件从页面视图树中卸载移除之前执行。

重写 will_unmount() 可以运行用于清理后台常驻服务、解除连接句柄的清理(clean-up)代码。

before_update() ​

每当控件进行状态刷新更新(update)时调用。

警告

请确保不要在 before_update() 内部调用控件自身的 update() 方法,否则会导致无限递归循环。


隔离控件 ​

自定义控件都拥有一个 is_isolated 属性,默认情况下该值为 False。

如果将 is_isolated 设置为 True,你的自定义控件将会与外部父级布局渲染更新完成隔离。这意味着:当外部父级控件触发 update() 方法重新渲染时,该控件自身会被渲染更新,但它子节点控件中的内部属性变更将不会被打包合并在全局更新包(update digest)中去。被隔离的控件应当由自身在类方法内显式调用 self.update() 来将自己的状态更改推送到 Flet 页面。

作为开发中的最佳实践:任何在其类方法中主动触发 self.update() 刷新的自定义控件,都强烈建议设置成隔离控件。

在前面的例子中,简单的样式化控件 MyButton 不需要设置隔离,而需要触发内部输入栏和编辑按钮显隐更新的组合控件 Task 应当实现隔离:

python
class Task(ft.Row):
    def __init__(self, text):
        super().__init__()

    def is_isolated(self):
        return True

关注我,学习大模型、大数据、Python等热门技术。