主题
声明式对话框 (Declarative dialogs)
ft.use_dialog() 允许组件以声明式的方式显示和更新对话框。你无需以命令式的方式调用 page.show_dialog() 并在稍后记住手动关闭或移除对话框,而是直接从组件状态中渲染 DialogControl:
- 传入对话框实例以显示它;
- 传入
None以隐藏它。
这使对话框逻辑与声明式应用的其余部分保持在相同的状态流中:
- 状态(state)决定了对话框是否可见;
- 状态改变时,对话框的内容也会随之更新;
- 组件中不需要调用
page.update()。
基本模式 (Basic pattern)
在每次渲染时调用 ft.use_dialog()。当对话框需要打开时,返回对话框控件;否则返回 None。
python
import flet as ft
@ft.component
def App():
show, set_show = ft.use_state(False)
ft.use_dialog(
ft.AlertDialog(
modal=True,
title=ft.Text("Delete report.pdf?"),
content=ft.Text("This cannot be undone."),
actions=[
ft.TextButton("Delete", on_click=lambda: set_show(False)),
ft.TextButton("Cancel", on_click=lambda: set_show(False)),
],
on_dismiss=lambda: set_show(False),
)
if show
else None
)
return ft.Column(
controls=[
ft.TextButton("Open dialog", on_click=lambda: set_show(True)),
]
)
ft.run(lambda page: page.render(App))最核心的一点是:show 是单一数据源(source of truth)。对话框并不是通过直接修改页面控件树来打开的,而是因为组件渲染了它,它才得以显示。
根据状态更新对话框内容 (Updating dialog content from state)
因为对话框是声明式的,它的内容可以在对话框打开时对状态变化做出响应。这对于确认框、表单验证和异步工作流非常有用:
python
import asyncio
import flet as ft
@ft.component
def App():
show, set_show = ft.use_state(False)
deleting, set_deleting = ft.use_state(False)
async def handle_delete():
set_deleting(True)
await asyncio.sleep(2)
set_deleting(False)
set_show(False)
ft.use_dialog(
ft.AlertDialog(
modal=True,
title=ft.Text("Delete report.pdf?"),
content=ft.Text(
"Deleting, please wait..." if deleting else "This cannot be undone."
),
actions=[
ft.Button(
"Deleting..." if deleting else "Delete",
disabled=deleting,
on_click=handle_delete,
),
ft.TextButton(
"Cancel",
disabled=deleting,
on_click=lambda: set_show(False),
),
],
on_dismiss=lambda: set_show(False),
)
if show
else None
)
return ft.TextButton("Delete file", on_click=lambda: set_show(True))
ft.run(lambda page: page.render(App))这种模式在 Flet 应用中与 asyncio 和其他异步 API 配合得非常好。有关更多背景信息,请参阅异步应用。
链式对话框 (Chaining dialogs)
你可以在同一个组件中多次调用 ft.use_dialog()。这使得后续的工作流变得非常直观,例如:
- 确认对话框;
- 异步操作;
- 第一个对话框完全关闭后,弹出成功对话框。
python
import flet as ft
@ft.component
def App():
show_confirm, set_show_confirm = ft.use_state(False)
show_success, set_show_success = ft.use_state(False)
should_chain = ft.use_ref(False)
def confirm_delete():
should_chain.current = True
set_show_confirm(False)
def on_confirm_dismiss():
if should_chain.current:
should_chain.current = False
set_show_success(True)
ft.use_dialog(
ft.AlertDialog(
title=ft.Text("Delete file?"),
actions=[
ft.TextButton("Delete", on_click=confirm_delete),
ft.TextButton("Cancel", on_click=lambda: set_show_confirm(False)),
],
on_dismiss=on_confirm_dismiss,
)
if show_confirm
else None
)
ft.use_dialog(
ft.AlertDialog(
title=ft.Text("Done"),
content=ft.Text("The file was deleted."),
actions=[
ft.TextButton("OK", on_click=lambda: set_show_success(False)),
],
)
if show_success
else None
)
return ft.TextButton("Open", on_click=lambda: set_show_confirm(True))
ft.run(lambda page: page.render(App))在这里使用 ft.use_ref() 非常有帮助,因为它的值在重新渲染时可以得以保留,而其本身的变化不会触发额外的重新渲染。
on_dismiss 触发时机 (on_dismiss timing)
DialogControl.on_dismiss 在对话框的关闭动画完成后触发,而不是在 open 变为 False 时立即触发。这使得在对话框确实完全关闭后安全地启动后续 UI 成为可能。
使用 on_dismiss 来处理需要在对话框完全消失后执行的逻辑,例如:
- 在对话框链中打开下一个对话框;
- 重置临时的对话框局部状态;
- 启动后续动画或显示 Toast 提示。
何时应该使用 page.show_dialog() 替代 (When to use page.show_dialog() instead)
ft.use_dialog() 更加契合 @ft.component 函数和其他声明式流。
但在以下情况下,page.show_dialog() 仍然是一个很好的选择:
- 应用程序采用命令式编写;
- 对话框的生命周期在组件树外部进行管理;
- 你需要从现有的页面级事件代码中触发对话框,并且暂时不想将该部分应用转换为声明式风格。
在实践中,这两种 API 分别服务于不同的风格:
- 当 UI 需要随组件状态改变而改变时,使用声明式对话框;
- 当 UI 是通过直接的页面修改来进行管理时,使用命令式对话框。