跳转至

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")

在主窗口按钮里:

button.New("打开计数器").OnClick(func() {
    application.Open(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,这条路径无法拦截。

主题与语言(按窗口)

application.SetTheme("main", theme.Dark())
application.SetLanguage("main", window.LanguageChinese)

在对应窗口的 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 中:

state := ctx.WindowState()
// state.Size, state.Mode, state.Focused, ...

也可用 Program.WindowStateMessage 把配置变化转成 Msg。

窗口动作(最小化/最大化等)见示例中的 WindowAction* 用法。

注意

  1. key 唯一:同一 Application 内窗口 key 不要冲突。
  2. Model 独立:每个 Program.Init 应返回新的 Model;不要共享可变指针。
  3. 不要跨窗口直接改对方 Model;用消息、共享服务或 Application 协调。
  4. Cmd 的 context 随该窗口生命周期取消。

示例

本章多窗口代码是用法片段;完整的应用生命周期测试位于仓库 uitest/app_test.go。

下一步