系统托盘 Tray
本教程共 42 篇 · 第 24 篇 · 更新于 2026-08-03
24. 系统托盘 Tray
本节目标
- 知道 v2.13.0 原生托盘接口的现状与限制。
- 用第三方 systray 库做出可运行的托盘图标和菜单。
- 配合 HideWindowOnClose 实现”关窗口不退出”的后台应用。
24-1 v2.13.0 的托盘现状
先说清楚一个容易踩坑的点:Wails v2.13.0 的核心并没有提供一个稳定、文档完备的系统托盘接口。
你在 options.App 里找不到 TrayMenu 这种字段。框架内部确实有一个 menu.TrayMenu 类型(第 24-4 节会列出来),但在 v2.13.0 里它没有被接到应用的公开配置上,生产环境没法直接用。
Warning网上有些老教程写
wails.SetSystemTray(...),那是 v1 的写法。v2 早已移除,绝对不能用,编译都过不了。本章不出现任何 v1 API。
那 v2 里怎么做托盘?社区最稳妥的做法是引入一个轻量的第三方库:github.com/getlantern/systray(或 github.com/gen2brain/systray)。它跨平台、体积小,和 Wails 的后端同为 Go,集成很自然。
Note为什么不直接用 Wails 的菜单包?因为 Wails 的
menu.Menu是给应用菜单条用的(第 23 章),托盘图标那一套在 v2.13.0 还没正式开放。第三方库补的就是这个空档。
24-2 用 systray 做托盘图标
先装库:
go get github.com/getlantern/systray
思路是:托盘在一条独立的 goroutine 里启动,不阻塞 wails.Run 的主流程。
package main
import (
"context"
"github.com/getlantern/systray"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
rt "github.com/wailsapp/wails/v2/pkg/runtime"
)
type App struct {
ctx context.Context
}
func (a *App) startup(ctx context.Context) { a.ctx = ctx }
func (a *App) ShowWindow() { rt.WindowShow(a.ctx) }
func (a *App) Quit() { rt.Quit(a.ctx) }
func main() {
app := &App()
go systray.Run(func() {
systray.SetTitle("码上学笔记")
systray.SetTooltip("码上学笔记")
mShow := systray.AddMenuItem("显示窗口", "显示主窗口")
mQuit := systray.AddMenuItem("退出", "退出程序")
go func() {
for {
select {
case <-mShow.ClickedCh:
app.ShowWindow()
case <-mQuit.ClickedCh:
systray.Quit()
app.Quit()
return
}
}
}()
}, func() {})
err := wails.Run(&options.App{
Title: "码上学笔记",
Width: 800,
Height: 600,
HideWindowOnClose: true,
OnStartup: app.startup,
Bind: []interface{}{app},
})
if err != nil {
systray.Quit()
panic(err)
}
}
systray.Run 的第一个函数里放初始化:设标题、加菜单项。菜单项的点击通过 ClickedCh 这个 channel 收,比回调更直观。
Warning在 macOS 上,systray 对主线程比较敏感。上面把
systray.Run放进 goroutine、wails.Run留在main是社区常用写法,但真机测试时若托盘不显示,可尝试反过来,把systray.Run放 main、Wails 放 goroutine。以你本机实测为准。
24-3 做后台应用
托盘最常见的场景:关掉窗口,程序还在后台跑,需要时从托盘唤出。
关键就是 HideWindowOnClose: true。加上它,点右上角关闭只是藏起窗口,程序不退出。
HideWindowOnClose: true,
再给前端一个”最小化到托盘”的按钮,调用第 19 章的隐藏方法:
function toTray() {
window.go.main.App.HideWindow();
}
Go 侧补一个 HideWindow:
func (a *App) HideWindow() { rt.WindowHide(a.ctx) }
用户从托盘点”显示窗口”,再 WindowShow 回来。整个生命周期都由托盘菜单掌控,体验就和微信、网盘客户端一样。
Tip想给托盘换图标,用
systray.SetIcon([]byte)传 PNG 数据。Windows 建议 16×16 或 32×32,macOS 建议 18~22 且用黑透模板图。用//go:embed把图标打进二进制最省事。
24-4 关于 menu.TrayMenu 类型
好奇心强的读者会翻到 Wails 源码里的 menu.TrayMenu。它长这样,列出来方便你将来升级时认得:
type TrayMenu struct {
Label string // 托盘文字
Image string // 图标资源名
MacTemplateImage bool // macOS 模板图
RGBA string // 文字颜色
FontSize int
FontName string
Tooltip string
Disabled bool
Menu *Menu // 关联的菜单
OnOpen func() // 菜单打开时
OnClose func() // 菜单关闭时
}
Note这个类型存在于
github.com/wailsapp/wails/v2/pkg/menu,但在 v2.13.0 里没有公开的options.App字段去挂载它。所以本章不依赖它写可运行代码,只作”将来可能有原生托盘”的铺垫。等官方把挂载点开放,迁移成本很低。
24-5 托盘与窗口显隐的完整交互
把托盘和窗口显隐打通。用户点托盘”显示窗口”,窗口从隐藏变可见并抢到焦点;点”隐藏”,窗口藏起但程序不退出;点”退出”,先停托盘再退程序。
func (a *App) ShowWindow() {
rt.WindowUnminimise(a.ctx)
rt.WindowShow(a.ctx)
}
func (a *App) HideToTray() {
rt.WindowHide(a.ctx)
}
前端”最小化到托盘”按钮直接调 HideToTray。托管的”显示窗口”调 ShowWindow。一条完整的后台应用闭环就成形了。
Tip窗口如果之前被最小化,
WindowShow可能只恢复不抢焦点。配合rt.WindowUnminimise(a.ctx)一起用,保证窗口真正回到前台。
24-6 图标与多平台注意
托盘图标是门面,多平台要求不同。
Windows 用 16×16 或 32×32 的 PNG/ICO,透明背景最好看。macOS 推荐 1822 的模板图,只用黑和透明,系统会自动适配深浅色。Linux 各桌面环境差异大,2248 的 PNG 或 SVG 更稳。
用 //go:embed 把图标打进二进制,避免依赖外部文件:
//go:embed icon.png
var iconBytes []byte
systray.SetIcon(iconBytes)
Note某些 Linux 桌面(尤其 Wayland 下的 GNOME)没有系统托盘区,图标可能根本不显示。这种环境要考虑降级,比如直接显示窗口而非依赖托盘。
24-7 托盘图标的点击行为
托盘图标本身也能响应点击,不只是菜单项。
getlantern/systray 提供了 SetOnClick,左键点图标就能触发:
systray.SetOnClick(func() {
app.ShowWindow() // 左键直接唤出窗口
})
这样用户左键点图标恢复窗口,右键弹菜单退出,是很多后台客户端的标准交互。
不同系统对左键、右键托盘图标的默认行为略有差异。Windows 上右键弹菜单更自然,macOS 上左键点图标就展开菜单。写跨平台时,把”显示窗口”和”退出”都放进右键菜单最稳妥,左键点击作为快捷方式即可。
托盘菜单项是可以动态增删的。比如同步状态变化时,把”同步中…”换成”已同步”。注意频繁重建会有轻微闪烁,状态类提示尽量复用同一个菜单项,只改它的文字。
Note托盘的”显示窗口”和”隐藏到托盘”要成对设计。用户从托盘恢复窗口后,如果关窗口又真退出了,体验就断了。务必配合
HideWindowOnClose: true,让关闭永远只是隐藏。
Tip想让托盘图标随状态变色(比如运行中是绿色、暂停是灰色),准备两套图标,在状态切换时调
systray.SetIcon换掉即可。注意 macOS 用模板图会自动适配深浅色,别硬套彩色图。
24-8 排错清单
托盘集成的坑。
第一,托盘图标不显示。Windows 检查 SetIcon 有没有传、尺寸对不对;macOS 检查模板图;Linux(尤其 Wayland GNOME)可能根本没托盘区,要考虑降级。
第二,点了菜单没反应。忘记起 goroutine 监听 ClickedCh,或 wails.Run 和 systray.Run 的线程位置在你的平台反了。
第三,关窗口程序就退。漏了 HideWindowOnClose: true,后台逻辑还没触发程序就真退了。
第四,误用 v1 的 wails.SetSystemTray。那是老 API,v2 编译不通,必须用第三方 systray 库。
Tip托盘调试建议先用最小例子:只放一个”退出”菜单项,能正常退出再慢慢加功能。托盘线程问题在不同平台表现不一,小步验证最稳。
常见误区
Warning任何
wails.SetSystemTray都是 v1 旧 API,v2 编译不通。托盘请走第三方 systray 库,本章示例已避开所有 v1 写法。
Note托盘菜单点击走
ClickedChchannel,要起一个 goroutine 用select常驻监听。忘记监听,点了菜单没有任何反应。
Tip
HideWindowOnClose是后台应用的核心。漏了它,用户一点关闭,托盘逻辑还没触发,整个程序就退出了。