11 · 多窗口¶
单窗口使用 ui.Run(Program)。需要多个独立窗口、托盘或应用生命周期控制时,使用 ui.Application。
概念¶
| 概念 | 说明 |
|---|---|
Application |
多窗口宿主 |
WindowSpec |
一个窗口的规格(key、初始化、Update、View、选项) |
| 窗口 key | 字符串身份,用于 Open / Close / SetTheme |
Application.Open |
返回 true 表示新窗口已启动;返回 false 表示同 key 的窗口已存在并被前置,或应用当前不接受新窗口 |
每个窗口有独立 Model 实例;初始化函数按实例执行,不要共享可变 Model。
使用 NewProgram 时,初始 Model 按值固定在 Program 中;如果 Model 含有
slice、map 或指针等引用类型,需要使用 Program.Init 在每次打开时创建新的值。
基本结构¶
func main() {
application := ui.NewApplication()
counter := ui.NewWindow("counter", ui.NewProgram(
CounterModel{}, counterUpdate, counterView(application),
),
window.Title("Counter"),
window.Size(400, 300),
)
mainWin := ui.NewWindow("main", ui.NewProgram(
MainModel{}, mainUpdate(application), mainView(application, counter),
),
window.Title("Main"),
window.Size(640, 480),
)
application.Run(mainWin)
}
需要初始命令、订阅或窗口状态消息时,把完整 Program 字面量传给 ui.NewWindow。
打开与关闭¶
if application.Open(counterSpec) {
// 新建并运行
} else {
// 同 key 已存在并已前置,或应用当前不接受新窗口
}
application.Close("counter")
在主窗口按钮里:
关闭生命周期¶
Gio 在 Windows、macOS、Wayland 和 X11 上以 app.ClosingEvent 报告原生关窗请求。
FlowUI 在窗口事件循环里处理它,并在平台真正关闭前应用关闭策略。
同步决策用 window.OnWindowCloseRequest:
window.OnWindowCloseRequest(func() window.CloseDecision {
if dirty {
return window.CloseCancel
}
return window.CloseProceed
})
CloseCancel:调用Abort(),窗口保留。CloseProceed:放行。CloseKeepAlive:放行原生关闭,同时启用进程保活(托盘/后台)。- 未知 decision:失败关闭(
Abort())。 Close/CloseAll/Quit:强制关闭,不经过 handler。
handler 在窗口事件循环 goroutine 上同步执行,必须快速返回,且拿不到 ui.Send,
不能自己打开 MVU 确认框。需要“原生 X → 应用内确认”时,设置
Program.CloseRequestMessage:FlowUI 会 Abort 原生请求并把返回的消息投给 Update。
var application *ui.Application
program := ui.Program[Model, Msg]{
// ...
CloseRequestMessage: func() Msg { return CloseRequested{} },
}
func update(m *Model, msg Msg) ui.Cmd[Msg] {
switch msg.(type) {
case CloseRequested:
if m.Dirty {
m.ConfirmClose = true
} else {
application.Close("main")
}
case DiscardAndClose:
m.ConfirmClose = false
application.Close("main")
}
return nil
}
application = ui.NewApplication()
application.Run(ui.NewWindow("main", program, window.Theme(theme.Light())))
消息钩子只在没有配置 OnWindowCloseRequest 时生效,显式 handler 优先;
没有 handler 时 RequestClose 会请求原生关闭,因此标题栏等应用内关闭入口也会走这个钩子。
Close 是最终强制关闭,不会再次进入钩子。ui.Run 会拒绝设置了
CloseRequestMessage 的 Program(它不暴露内部 Application);需要在 Update
里关闭窗口时改用 ui.NewApplication + Application.Run。
macOS 的应用终止可能不经过 ClosingEvent,这条路径无法拦截。
主题与语言(按窗口)¶
在对应窗口的 Update 中根据消息调用;默认主题使用 github.com/qianniancn/flowui/theme。
不要在任意 goroutine 里直接改主题;走 Application API,以便在目标窗口事件循环上应用。
窗口可以在创建时指定完整的 BCP-47 语言标签,并为 FlowUI 内置文案提供应用级覆盖:
messages := i18n.NewCatalog().With("fr", i18n.ActionClose, "Fermer")
windowSpec := ui.NewWindow("settings", ui.NewProgram(
SettingsModel{}, settingsUpdate, settingsView,
),
window.Locale(i18n.Language("fr-CA")),
window.Translations(messages),
)
Catalog 只负责 FlowUI 拥有的文案。menu.Item.Label、业务按钮标题和用户数据不会被自动翻译。DatePicker 的月份名、星期名和月份-年份标题由 Catalog 提供,可通过 window.Translations 覆盖;日期顺序、分隔符、周起始日和数字格式属于日期领域格式,由窗口语言提供默认值,显式的 datepicker.Locale(...) 会覆盖它。比如 zh-CN 会使用中文 UI 和中文日期默认格式,但可以在单个 DatePicker 上显式选择 datepicker.English()。
窗口状态¶
在 View 中:
也可用 Program.WindowStateMessage 把配置变化转成 Msg。
窗口动作(最小化/最大化等)见示例中的 WindowAction* 用法。
注意¶
- key 唯一:同一 Application 内窗口 key 不要冲突。
- Model 独立:每个
Program.Init应返回新的 Model;不要共享可变指针。 - 不要跨窗口直接改对方 Model;用消息、共享服务或 Application 协调。
- Cmd 的 context 随该窗口生命周期取消。
示例¶
本章多窗口代码是用法片段;完整的应用生命周期测试位于仓库 uitest/app_test.go。