窗口管理 Window API
本教程共 42 篇 · 第 19 篇 · 更新于 2026-08-03
19. 窗口管理 Window API
本节目标
- 掌握 runtime.Window 包提供的窗口控制方法。
- 能在 Go 和前端 TypeScript 里调用同一套窗口接口。
- 实现标题修改、尺寸调整、位置移动、全屏与显隐等常见交互。
19-1 什么是 Window 运行时
窗口刚启动时,大小、标题都写在 options 里定死了。
可很多时候,我们要在程序跑起来之后再改窗口。
比如点击按钮让窗口居中,或者收进任务栏。
这些”跑起来再改”的动作,就交给 runtime.Window 包来做。
Noteruntime 是 Wails 的运行时包,分 Go 和 JavaScript 两套接口。Go 侧用
rt.WindowXxx(ctx, ...),前端用window.runtime.WindowXxx(...),两边方法名一一对应,只差 Go 版本第一个参数是 context。学一个就会两个。
Go 侧先引入别名,避免和标准库 runtime 撞名:
import rt "github.com/wailsapp/wails/v2/pkg/runtime"
前端有两条路可走。第一条是直接用 JS 运行时,不用在 Go 里写任何绑定,方法名和 Go 侧完全一样:
import { WindowSetTitle } from "../wailsjs/runtime/runtime";
WindowSetTitle("新标题");
第二条是自己在 Go 里封一个方法再绑定给前端。适合”改标题的同时还要写日志、存配置”这类顺带要做后端事情的场景。默认模板里后端结构体是 App、包名是 main,调用路径就是:
await window.go.main.App.SetTitle("新标题");
下面把常用方法分组讲清楚,每个都给 Go 和前端两套写法。
19-2 改标题与显隐窗口
最常用的是改标题。Go 侧一行搞定:
func (a *App) SetTitle(title string) {
rt.WindowSetTitle(a.ctx, title)
}
前端对应的 TypeScript 调用:
function rename() {
window.go.main.App.SetTitle("码上学笔记");
}
隐藏和显示也很直接。想关掉窗口却留住程序,靠 WindowHide 和 WindowShow:
func (a *App) Hide() { rt.WindowHide(a.ctx) }
func (a *App) Show() { rt.WindowShow(a.ctx) }
Tip做”最小化到托盘”的效果,可以先
Hide窗口,等用户点托盘再Show。配合下一章的HideWindowOnClose更顺手。
WindowIsNormal 告诉你窗口当前是不是普通状态。它只有在”不是最大化、不是最小化、也不是全屏”时才返回 true。
19-3 调尺寸与移动位置
调整窗口大小用 WindowSetSize,参数是宽和高:
func (a *App) Resize(w, h int) {
rt.WindowSetSize(a.ctx, w, h)
}
读取当前大小用 WindowGetSize。Go 侧一次返回宽、高两个 int,前端返回的是 Promise<Size>,结构是 { w, h }:
import { WindowGetSize } from "../wailsjs/runtime/runtime";
const size = await WindowGetSize();
console.log(size.w, size.h);
移动位置用 WindowSetPosition,坐标是相对当前显示器左上角的:
func (a *App) MoveTo(x, y int) {
rt.WindowSetPosition(a.ctx, x, y)
}
读取位置返回 Position,结构是 { x, y }。
最小和最大尺寸也能在运行时改。传 0, 0 就取消限制:
rt.WindowSetMinSize(a.ctx, 400, 300)
rt.WindowSetMaxSize(a.ctx, 1280, 800)
19-4 全屏、最大化与最小化
全屏切换有三件套:WindowFullscreen、WindowUnfullscreen、WindowIsFullscreen。
func (a *App) ToggleFullscreen() {
if rt.WindowIsFullscreen(a.ctx) {
rt.WindowUnfullscreen(a.ctx)
} else {
rt.WindowFullscreen(a.ctx)
}
}
最大化同样有开、关、查三态:
rt.WindowMaximise(a.ctx) // 最大化
rt.WindowUnmaximise(a.ctx) // 还原
rt.WindowIsMaximised(a.ctx) // 是否最大化
rt.WindowToggleMaximise(a.ctx) // 一键切换
最小化也一样:
rt.WindowMinimise(a.ctx) // 最小化
rt.WindowUnminimise(a.ctx) // 还原
rt.WindowIsMinimised(a.ctx) // 是否最小化
让窗口回到屏幕正中,用 WindowCenter。它针对窗口当前所在的显示器生效:
rt.WindowCenter(a.ctx)
19-5 主题、置顶与打印
Windows 平台支持切换窗口主题。三个方法对应系统默认、浅色、深色:
rt.WindowSetSystemDefaultTheme(a.ctx)
rt.WindowSetLightTheme(a.ctx)
rt.WindowSetDarkTheme(a.ctx)
Warning上面三个主题方法仅 Windows 有效。在 macOS 或 Linux 调用不会报错,也不会有任何效果。跨平台代码里要先判断系统。
让窗口始终盖在最上面,用 WindowSetAlwaysOnTop,参数是布尔值:
rt.WindowSetAlwaysOnTop(a.ctx, true)
运行时换背景色用 WindowSetBackgroundColour,四个参数是 R、G、B、A,取值范围都是 0~255。这个颜色会从所有透明像素底下透出来:
rt.WindowSetBackgroundColour(a.ctx, 0, 0, 0, 255) // 不透明黑
NoteWindows 上 Alpha 只认 0 和 255 两档,任何非 0 的值都会被当成 255。要做真正的半透明窗口,得配合下一章的
WebviewIsTransparent和WindowIsTranslucent。
想重新加载界面,有两个选择。WindowReload 只刷新当前页面,WindowReloadApp 会重建整个前端:
rt.WindowReload(a.ctx) // 类似浏览器 F5
rt.WindowReloadApp(a.ctx) // 重建前端资源
需要往页面注入一段 JS,用 WindowExecJS。它是异步执行,报错只会出现在浏览器控制台:
rt.WindowExecJS(a.ctx, "console.log('来自 Go 的脚本')")
最后,WindowPrint 会唤起系统打印对话框,适合做小票、报表类功能。
19-6 一个综合小例子
把前面这些方法串起来,才有体感。设想一个设置面板:用户点按钮就能切换窗口置顶、切换全屏、让窗口居中,或者暂时藏起窗口。
Go 侧把几个方法包成绑定:
func (a *App) TogglePin() {
a.pinned = !a.pinned
rt.WindowSetAlwaysOnTop(a.ctx, a.pinned)
}
func (a *App) CenterWindow() { rt.WindowCenter(a.ctx) }
前端放三个按钮,分别调 TogglePin、ToggleFullscreen、CenterWindow。这样窗口控制完全在界面里完成,不用去碰系统菜单。
Tip置顶这类”开/关”状态,存在结构体字段里,切换时取反最省事。前端想回显当前状态,再暴露一个
GetPinned方法读出来就行。
19-7 方法速查表
最后给一张常用方法速查,方便回头翻:
| 功能 | Go 方法 |
|---|---|
| 改标题 | WindowSetTitle |
| 显示 / 隐藏 | WindowShow / WindowHide |
| 是否普通态 | WindowIsNormal |
| 设尺寸 | WindowSetSize |
| 读尺寸 | WindowGetSize |
| 最小 / 最大尺寸 | WindowSetMinSize / WindowSetMaxSize |
| 设位置 | WindowSetPosition |
| 读位置 | WindowGetPosition |
| 居中 | WindowCenter |
| 全屏 | WindowFullscreen / WindowUnfullscreen / WindowIsFullscreen |
| 最大化 | WindowMaximise / WindowUnmaximise / WindowToggleMaximise |
| 最小化 | WindowMinimise / WindowUnminimise |
| 置顶 | WindowSetAlwaysOnTop |
| 背景色 | WindowSetBackgroundColour |
| 主题(Windows) | WindowSetSystemDefaultTheme / SetLightTheme / SetDarkTheme |
| 打印 | WindowPrint |
| 注入脚本 | WindowExecJS |
| 刷新 | WindowReload / WindowReloadApp |
前端对应的名字完全一致,只是换成 window.runtime.WindowXxx() 这条运行时路径(也可以从 ../wailsjs/runtime/runtime 里 import,有类型提示)。除了 WindowExecJS 只有 Go 侧提供,其余方法两边都有。
19-8 用 ExecJS 做主题切换
再讲一个 WindowExecJS 的实用场景:动态切深色模式。
前端把深色样式写在一个 class 上,Go 侧通过注入脚本去 toggle:
func (a *App) ToggleDark(dark bool) {
if dark {
rt.WindowExecJS(a.ctx, `document.body.classList.add('dark')`)
} else {
rt.WindowExecJS(a.ctx, `document.body.classList.remove('dark')`)
}
}
WindowExecJS 是异步的,脚本里如果报错,只会在浏览器控制台出现,Go 侧收不到。所以它适合”下指令”,不适合”拿返回值”。
Warning别用
WindowExecJS去做需要返回值的复杂计算。要拿结果,应该在前端用 Promise 调 Go 的绑定方法,方向别反了。
Tip注入的脚本字符串里如果含反引号或双引号,记得转义。复杂逻辑建议抽成前端函数,Go 只调一个简短的调用,比如
app.switchTheme('dark'),可读性高很多。
19-9 排错清单
写窗口控制时最常卡住的地方,列出来帮你省时间。
第一,前端调用忘了 await,结果读到的尺寸是上一帧的旧值。凡是返回 Promise 的,都加上 await。
第二,拿不到 ctx。所有 Window 方法都要一个有效的 context.Context,它来自 OnStartup 或绑定方法接收的上下文。自己 context.Background() 造一个,窗口找不到目标,调用会静默失败。
第三,macOS 上主题方法没反应。那三个主题切换只在 Windows 有效,苹果和 Linux 调了也不会报错,只是没效果,别以为是代码写错。
第四,坐标对不上。前端拿到的 Position 是相对当前显示器的,和屏幕全局坐标不是一回事。做跨屏定位时要自己换算。
Tip调试窗口行为,最方便是在
wails dev下开浏览器开发者工具,直接在前端控制台敲window.runtime.WindowGetSize()看返回值,比反复改代码编译快得多。
常见误区
Warning前端调用都返回 Promise。忘记
await会导致顺序错乱,比如先读尺寸再改尺寸却拿到旧值。
Note坐标
Position和尺寸Size在前端是对象,不是数组。size.w和pos.x才是正确取法。
Tip所有 Window 方法第一参数都是
context.Context。这个 ctx 来自OnStartup或绑定方法的接收,不要自己context.Background()硬凑,否则窗口找不到目标。