实践范例:模态对话框¶
模态框就是一个你按条件返回的元素。并没有什么特殊的
「打开对话框」API —— 当 open 为 true 时,树里会包含叠在页面之上的模态
面板;为 false 时该模态元素根本不存在,页面单独渲染。
原语¶
| 关注点 | API |
|---|---|
| 开/关状态 | UseState<bool> |
| 条件层次 | open ? Group(page, modal) : page |
| 遮罩 | 带半透明填充的外层 Border |
| 按钮 | Button(label, onClick) |
| 生产级替代 | ContentDialog |
状态¶
两个布尔值 —— 一个用于开/关标志,一个用于记录结果。
删除分支会翻转两者;取消分支只翻转
open。
页面¶
// The page renders normally; the modal is just another element
// returned conditionally based on `open`.
var page = VStack(12,
TextBlock(deleted ? "Item deleted." : "1 item selected."),
Button("Delete…", () => setOpen(true))
).Padding(20);
页面照常渲染。按钮更新 open,从而触发一次包含该模态框的
重渲染。
模态面板¶
// Pair the dialog with a scrim (SmokeFill) so clicks outside the
// dialog don't reach the page underneath.
Element modal = Border(
VStack(16,
Heading("Delete this item?"),
TextBlock("This action cannot be undone.").Opacity(0.8),
HStack(8,
Button("Cancel", () => setOpen(false)),
Button("Delete", () => { setDeleted(true); setOpen(false); })
).HAlign(Microsoft.UI.Xaml.HorizontalAlignment.Right)
).Padding(20).Background(Theme.CardBackground).CornerRadius(8)
).Background(Theme.SmokeFill).Padding(40);

该面板是一个带彩色遮罩背景的 Border。两个按钮闭包捕获了同一组
setOpen 与 setDeleted setter,因此「取消」能干净地回退,而「删除」会在
同一次渲染中提交操作并关闭模态框。
在真实应用中,请把这个 Border 换成
dialogs-and-flyouts 里的 ContentDialog,这样焦点陷阱、
按 Esc 取消以及屏幕阅读器语义都能免费获得;上面这段范例只是用显式原语
表述了同样的形态,好让组合过程可见。
提示¶
把模态框渲染为兄弟节点,而不是触发按钮的子节点。
把模态框放在按钮内部会让它按按钮的边界框参与布局;通过
Group(page, modal) 渲染它,模态框才能按预期铺满宿主窗口。
在真实应用里,要在模态 Border 上做焦点陷阱。 上面的范例为了可读性省略了
焦点管理 —— 生产代码应在这个面板上调用
.FocusTrap(...),让 Tab 在对话框内部循环。完整模式见
无障碍。
不要把「打开」和「保存中」混为一谈。 确认并提交的模态框有
三种状态:关闭、打开、提交中。当提交是异步的时,为「进行中」这一位再加一个
UseState<bool> —— 模式与登录范例相同。