跳转到内容

窗口管理 API

一个 App 管理主窗口及零个或多个独立顶层子窗口。组件 API 仍统一来自 dxui 根包;Window 是生命周期句柄,不能作为 View 使用,也不要自行构造零值 Window。先读多窗口教程,精确声明见运行时参考

创建与调度

API参数与结果范围、限制与错误
App.CreateWindow(options WindowOptions, root func() View) (*Window, error)配置和非 nil 根构建函数;成功返回子窗口句柄。UI 回调或 App.Update 内调用。准备首帧后显示;失败清理已创建资源,不影响已有窗口。
App.Update(update func()) errorFIFO 排队执行非 nil 状态修改。仅主窗口;可跨 goroutine。未运行/关闭返回 ErrAppNotRunning/ErrAppClosed。
App.Invalidate() error请求主窗口重新构建,不附带状态修改。可跨 goroutine;生命周期错误同 App.Update。不能替代后台状态同步。
Window.Update(update func()) errorFIFO 排队执行非 nil 状态修改。仅目标子窗口;可跨 goroutine。已关闭或 nil 句柄返回 ErrWindowClosed,App 正在退出可返回 ErrAppClosed。
Window.Invalidate() error请求目标子窗口重建。相当于对该窗口提交空更新;生命周期错误同 Window.Update。
Window.Close()请求关闭目标子窗口,无返回值。幂等、可跨 goroutine;不关闭主窗口或兄弟窗口,不调用 OnCloseRequest。nil 句柄为空操作。
Window.Closed() bool返回已请求或完成关闭。并发安全,nil 句柄返回 true;不能作为下一次操作必定成功的保证。
Window.Diagnostics() RuntimeDiagnostics返回该窗口的计数快照。并发安全;nil 句柄返回零快照。字段解释

App.Close 关闭全部窗口并结束唯一事件循环;App.Diagnostics 仍以主窗口为对象,不是全部窗口统计之和。关闭后不要继续提交依赖该窗口存活的工作。

WindowOptions 全部字段

字段类型默认值与行为
Titlestring空字符串默认 dxui。
Width / Heightfloat32默认 640 / 480 逻辑单位;不同于主窗口默认的 800 / 600。有限非负,不超过 MaxInt32;显式正值四舍五入后至少为 1。
MinWidth / MinHeightfloat320 不设置最小尺寸;有限非负,不超过 MaxInt32。
BackgroundRGBAColor全零使用 App 配置的背景;若仍全零,回退 RGBA(28,30,36,255)。
Shortcuts[]Shortcut独立、复制的窗口快捷键;默认空,不自动继承 AppOptions.Shortcuts。
OnCloseRequestfunc(*Window)nil 接受系统关闭请求;非 nil 时调用 Window.Close 才接受,否则拒绝。
OnShownfunc(*Window)完整首帧呈现并显示后仅调用一次;在 CreateWindow 返回前执行,使用回调传入的句柄。

Theme、Fonts、DefaultFont、DisableSystemFontFallback、Renderer、Caches、Diagnostics 等使用 App 配置。窗口不暴露独立 SetTheme:通过 App.SetTheme 更新所有存活窗口。预算按窗口分别使用,创建更多窗口会增加总资源容量。

源码 WindowOptions 注释目前笼统写了继承 App 快捷键,但创建实现明确用 options.Shortcuts 替换。因此本页与展示参考按实现说明为窗口独立配置,完整原始注释保留在维护快照中。

主窗口与子窗口通用操作

下面每个方法同时存在于 *App(主窗口)和 *Window(指定子窗口)。带原生操作的 SetTitle、SetSize、Maximize、Unmaximize、Minimize、Unminimize 必须在 UI 回调或 Update 闭包执行;Title、Size、IsMaximized、IsMinimized 是并发安全快照。

方法返回值用途与限制
Title()string最近成功配置的标题;App 未运行时返回 AppOptions 的原始标题。nil Window 返回空字符串。
SetTitle(title string)error成功更新标题快照,不重建根;空字符串按请求设置,不套用创建时默认标题。
Size()Size逻辑客户区尺寸;成功 SetSize 与后续 resize/scale 事件均更新快照。App 未运行时返回配置尺寸;nil Window 返回零 Size。
SetSize(width, height float32)error两轴必须有限正数,四舍五入后至少 1,不超过 MaxInt32。请求成功更新快照,实际布局跟随后续视口事件,平台仍可能调整最终尺寸。
Maximize()error请求平台最大化;返回 nil 不代表状态事件已确认。
Unmaximize()error请求恢复正常状态。
Minimize()error请求平台最小化;返回 nil 不代表状态事件已确认。
Unminimize()error请求恢复正常状态;与 Unmaximize 共用平台恢复操作。
IsMaximized()bool最近窗口事件确认的最大化状态,不根据请求乐观设置;未创建主窗口或 nil Window 返回 false。
IsMinimized()bool最近窗口事件确认的最小化状态;未创建主窗口或 nil Window 返回 false。

主窗口命令在运行前返回 ErrWindowNotRunning,在 App 关闭后返回 ErrAppClosed。子窗口关闭后的命令返回 ErrWindowClosed;标题/尺寸/状态读取仍是快照,不用它们判断窗口是否存活。

WindowOptions 不提供状态事件回调。若把 IsMaximized/IsMinimized 显示为文字,不应在发出请求后立即假定它已变化;下面的 Read confirmed state 按钮在点击时重新读取已确认状态。

完整操作示例

这个例子给主窗口和子窗口提供同样的标题、尺寸、最大化与最小化按钮。子窗口首次构建早于 CreateWindow 返回,因此通过函数取得句柄,只在之后的按钮事件中执行操作。

go
package main

import (
	"fmt"
	"log"

	"github.com/dxui-org/dxui"
)

// App and Window share these window-level operations.
type windowControl interface {
	Title() string
	SetTitle(string) error
	Size() dxui.Size
	SetSize(float32, float32) error
	Maximize() error
	Unmaximize() error
	Minimize() error
	Unminimize() error
	IsMaximized() bool
	IsMinimized() bool
	Invalidate() error
	Diagnostics() dxui.RuntimeDiagnostics
}

func controls(target func() windowControl, message *string) dxui.View {
	action := func(label string, run func() error) dxui.View {
		return dxui.TextButton(
			dxui.ButtonProps{
				OnPress: func() {
					if err := run(); err != nil {
						*message = err.Error()
						return
					}
					*message = "Requested: " + label
				},
			},
			label,
		)
	}
	row := func(children ...dxui.View) dxui.View {
		return dxui.Box(dxui.BoxProps{
			Direction: dxui.Horizontal,
			Gap:       8,
		}, children...)
	}
	return dxui.Box(
		dxui.BoxProps{
			Gap: 12,
		},
		row(
			action("Set title", func() error {
				return target().SetTitle("Updated title")
			}),
			action("Set size", func() error {
				return target().SetSize(720, 600)
			}),
		),
		row(
			action("Maximize", func() error {
				return target().Maximize()
			}),
			action("Unmaximize", func() error {
				return target().Unmaximize()
			}),
		),
		row(
			action("Minimize", func() error {
				return target().Minimize()
			}),
			action("Unminimize", func() error {
				return target().Unminimize()
			}),
		),
		row(
			action("Refresh root", func() error {
				return target().Invalidate()
			}),
			dxui.TextButton(
				dxui.ButtonProps{
					OnPress: func() {
						w := target()
						*message = fmt.Sprintf(
							"%s | size=%v | maximized=%t | minimized=%t",
							w.Title(),
							w.Size(),
							w.IsMaximized(),
							w.IsMinimized(),
						)
					},
				},
				"Read confirmed state",
			),
		),
		dxui.TextButton(
			dxui.ButtonProps{
				OnPress: func() {
					d := target().Diagnostics()
					*message = fmt.Sprintf("Builds: %d, paints: %d", d.BuildCount, d.PaintCount)
				},
			},
			"Snapshot",
		),
		dxui.Text(dxui.TextProps{
			Value: *message,
			Wrap:  dxui.TextWrapWords,
		}),
	)
}

func main() {
	mainMessage, childMessage := "Ready", "Ready"
	var child *dxui.Window
	app := dxui.NewApp(dxui.AppOptions{
		Title:       "Window controls",
		Width:       720,
		Height:      600,
		Diagnostics: true,
		Background:  dxui.RGBA(248, 250, 252, 255),
	})
	open := func() {
		if child != nil && !child.Closed() {
			mainMessage = "Child is already open"
			return
		}
		childMessage = "Ready"
		window, err := app.CreateWindow(
			dxui.WindowOptions{
				Title:  "Child controls",
				Width:  720,
				Height: 600,
			},
			func() dxui.View {
				return dxui.Box(
					dxui.BoxProps{
						Style: dxui.Style{
							Padding: dxui.Padding(24),
						},
					},
					controls(func() windowControl {
						return child
					}, &childMessage),
				)
			},
		)
		if err != nil {
			mainMessage = err.Error()
			return
		}
		child = window
		mainMessage = "Child opened"
	}
	if err := app.Run(func() dxui.View {
		return dxui.Box(
			dxui.BoxProps{
				Gap: 16,
				Style: dxui.Style{
					Padding: dxui.Padding(24),
				},
			},
			dxui.TextButton(dxui.ButtonProps{
				OnPress: open,
			}, "Open child"),
			dxui.TextButton(
				dxui.ButtonProps{
					OnPress: func() {
						if child == nil || child.Closed() {
							mainMessage = "Open a child first"
							return
						}
						if err := child.Unminimize(); err != nil {
							mainMessage = err.Error()
						}
					},
				},
				"Restore child",
			),
			controls(func() windowControl {
				return app
			}, &mainMessage),
		)
	}); err != nil {
		log.Fatal(err)
	}
}

最小化后可从系统任务栏或窗口管理器恢复窗口,或通过主窗口的 Restore child 按钮请求恢复。Refresh root 演示目标 Invalidate,Snapshot 演示目标 Diagnostics;这些接口不会创建轮询循环。

生命周期错误

错误对应场景
ErrWindowNotRunningCreateWindow 在 App.Run 之外执行,或主窗口命令在运行前执行。
ErrWindowClosednil/已关闭子窗口的 Update、Invalidate 或窗口命令。
ErrAppClosedApp 正在退出时提交工作或执行主窗口命令。
其它 errornil builder/更新闭包、非法尺寸、快捷键校验、首帧构建或平台操作失败。

使用 errors.Is 区分生命周期错误,处理返回值,不匹配错误文本。错误校验有先后顺序;例如 CreateWindow 在运行时已销毁后可能首先返回 ErrWindowNotRunning,不应把所有退出阶段一律看成同一种错误。