跳转到内容

多窗口与独立更新

这一节从主窗口打开子窗口,并分别修改它们的内容。每个子窗口都有自己的输入框和计数器。

完整示例

go
package main

import (
	"errors"
	"fmt"
	"log"

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

type childState struct {
	window     *dxui.Window
	count      int
	text       string
	allowClose bool
	message    string
}

func report(err error) {
	if err != nil && !errors.Is(err, dxui.ErrWindowClosed) && !errors.Is(err, dxui.ErrAppClosed) {
		log.Print(err)
	}
}

func main() {
	app := dxui.NewApp(dxui.AppOptions{
		Title:      "Multi-window",
		Width:      640,
		Height:     420,
		Background: dxui.RGBA(248, 250, 252, 255),
	})
	message := "Create independent child windows"
	children := []*childState{}
	create := func() {
		number := len(children) + 1
		state := &childState{
			allowClose: true,
		}
		requestClose := func(w *dxui.Window) {
			if !state.allowClose {
				state.message = "Enable Allow close first"
				return
			}
			w.Close()
			report(app.Update(func() {
				message = fmt.Sprintf("Child %d closed", number)
			}))
		}
		window, err := app.CreateWindow(
			dxui.WindowOptions{
				Title:          fmt.Sprintf("Child %d", number),
				Width:          420,
				Height:         400,
				OnCloseRequest: requestClose,
				OnShown: func(w *dxui.Window) {
					log.Printf("Shown: %s", w.Title())
				},
			},
			func() dxui.View {
				return dxui.Box(
					dxui.BoxProps{
						Gap: 12,
						Style: dxui.Style{
							Padding: dxui.Padding(24),
						},
					},
					dxui.Label(fmt.Sprintf("Count: %d", state.count)),
					dxui.Input(dxui.InputProps{
						Key:         "notes",
						Value:       state.text,
						Placeholder: "Independent input",
						OnChange:    dxui.Assign(&state.text),
					}),
					dxui.TextButton(
						dxui.ButtonProps{
							OnPress: func() {
								state.count++
								report(app.Update(func() {
									message = fmt.Sprintf("Child %d changed", number)
								}))
							},
						},
						"Increment this child",
					),
					dxui.Checkbox(
						dxui.CheckboxProps{
							Checked:  state.allowClose,
							OnChange: dxui.Assign(&state.allowClose),
						},
						dxui.Label("Allow close"),
					),
					dxui.Label(state.message),
					dxui.TextButton(
						dxui.ButtonProps{
							OnPress: func() {
								requestClose(state.window)
							},
						},
						"Close this child",
					),
				)
			},
		)
		if err != nil {
			message = err.Error()
			return
		}
		state.window = window
		children = append(children, state)
		message = fmt.Sprintf("Created child %d", number)
	}
	updateNewest := func() {
		for i := len(children) - 1; i >= 0; i-- {
			state := children[i]
			if !state.window.Closed() {
				report(state.window.Update(func() {
					state.count++
				}))
				return
			}
		}
	}
	if err := app.Run(func() dxui.View {
		return dxui.Box(
			dxui.BoxProps{
				Gap: 16,
				Style: dxui.Style{
					Padding: dxui.Padding(24),
				},
			},
			dxui.Label(message),
			dxui.TextButton(dxui.ButtonProps{
				OnPress: create,
			}, "Create child"),
			dxui.TextButton(
				dxui.ButtonProps{
					OnPress: updateNewest,
				},
				"Update newest child",
			),
			dxui.TextButton(dxui.ButtonProps{
				OnPress: app.Close,
			}, "Close all windows"),
		)
	}); err != nil {
		log.Fatal(err)
	}
}

1. 打开两个子窗口

运行后点击两次 Create child,然后在两个子窗口中输入不同文字、点击 Increment this child。各窗口的文字和数字互不影响。

创建窗口使用 app.CreateWindow:第一个参数设置标题和尺寸,第二个参数描述窗口中的控件。它在按钮回调中调用,不要放进构建函数,也不要从 goroutine 直接调用。

2. 从主窗口更新子窗口

点击主窗口的 Update newest child,最近打开且未关闭的子窗口数字加一。

要更新哪里使用方式
当前窗口的组件回调直接修改当前窗口的状态。
主窗口app.Update 包住状态修改。
某个子窗口window.Update 包住状态修改。

共享一个 Go 变量不会自动刷新所有窗口。示例在子窗口中使用 app.Update 更新主窗口的提示文字。

3. 关闭窗口

取消子窗口的 Allow close 勾选,再点击关闭按钮,窗口会保留。重新勾选后即可关闭。

Window.Close 只关闭一个子窗口;App.Close 或关闭主窗口会结束整个应用,不逐一询问子窗口。提交更新时仍要处理错误,因为目标窗口可能已关闭。

创建窗口时的两个注意点

第一次构建发生在 CreateWindow 返回前。构建函数中不要立即读取外层尚未赋值的窗口句柄;示例只在之后的按钮回调中使用它。

OnShown 同样在 CreateWindow 返回前执行,需要句柄时使用回调传入的 *Window

主题通过 App.SetTheme 统一切换;子窗口快捷键需要单独设置。更多配置、刷新方法、窗口尺寸与最大化操作见窗口管理 API

下一步:遇到问题时排查