Wails v3 系统托盘实战指南:用 systray-basic 示例实现托盘图标与附属窗口
2026/9/19 14:09:58 网站建设 项目流程

Wails v3 系统托盘实战指南:用 systray-basic 示例实现托盘图标与附属窗口

【免费下载链接】wailsCreate beautiful applications using Go项目地址: https://gitcode.com/gh_mirrors/wa/wails

Wails v3 为 Go 开发者提供了跨平台系统托盘(System Tray)能力,可在应用退出后常驻菜单栏/通知区域。本指南以官方示例 v3/examples/systray-basic/README.md 及完整实现 main.go 为骨架,结合 v3/pkg/application/systemtray.go 与 v3/pkg/application/system_tray_manager.go 的源码,深入解析托盘生命周期、附属窗口(Attached Window)、点击切换与失焦隐藏机制,让你可以基于此模板快速打造自己的常驻托盘应用。

示例概览:systray-basic 做了什么

systray-basic 是一个演示 Wails v3 系统托盘 API 的最小可运行示例,核心行为如下:

  • 创建一个系统托盘(systray)并附带一个 Webview 窗口;
  • 窗口默认隐藏,左键单击托盘图标时切换显示/隐藏
  • 窗口失去焦点时自动隐藏;
  • 在 Windows 上,如果图标位于通知栏的弹出区域(notification flyout),窗口会显示在右下角靠近图标的位置。

官方状态表如下:

平台状态
MacWorking(可用)
WindowsWorking(可用)
Linux未标记

Linux 状态留白,意味着当前示例在 Linux 上的表现未被官方验证;实际功能取决于平台实现(见 systemtray_linux.go)。从源码结构看,Linux 托盘仍处于演进中,使用时应以实测为准。

构建并运行示例

示例目录内没有独立的go.mod,它依赖仓库根目录 v3/go.mod 模块。运行方式:

# 在 v3 模块内执行(示例属于 wails/v3 主模块) go run ./examples/systray-basic

运行后:

  1. macOS:菜单栏出现托盘图标,Dock 中不显示常规应用图标(因为启用了 Accessory 激活策略);
  2. Windows:任务栏通知区域出现图标,应用主窗口不占用任务栏;
  3. 左键单击图标,500×500 的窗口在图标附近弹出;再次单击或点击窗口外部,窗口隐藏。

核心实现拆解

整个示例逻辑集中在 main.go 的main()函数中,可分为四步:创建应用、创建托盘、创建附属窗口、绑定交互。

第一步:创建应用

app := application.New(application.Options{ Name: "Systray Demo", Description: "A demo of the Systray API", Assets: application.AlphaAssets, Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory, }, })

要点说明:

  • Name/Description:应用元信息,会体现在托盘 Tooltip 与系统相关界面中;
  • Assets: application.AlphaAssets:使用内置的 Alpha 测试资产作为前端资源,适合快速体验,无需单独准备前端目录;
  • Mac.ActivationPolicy:设置为ActivationPolicyAccessory(值为 1,见 application_options.go)。该策略专为无主窗口的后台/托盘应用设计,使应用不出现在 Dock 中,配合菜单栏托盘图标构成典型 macOS 工具型应用形态。

第二步:创建系统托盘

systemTray := app.SystemTray.New()

SystemTray是 Wails v3 顶层管理器App.SystemTray暴露的能力。看 system_tray_manager.go 的实现:New()会生成自增 ID,将托盘注册到app.systemTrays映射中,并通过runOrDeferToAppRun保证无论调用时机如何,托盘都会在应用运行阶段完成平台初始化。

在 systemtray.go 的构造函数中,新托盘默认携带:

attachedWindow: WindowAttachConfig{ Window: nil, Offset: 0, Debounce: 200 * time.Millisecond, }

也就是说Debounce默认 200ms,用于 Windows 上抑制"隐藏后立刻又显示"的抖动。

第三步:创建附属窗口(Attached Window)

window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Width: 500, Height: 500, Name: "Systray Demo Window", Frameless: true, AlwaysOnTop: true, Hidden: true, DisableResize: true, HideOnEscape: true, HideOnFocusLost: true, Windows: application.WindowsWindow{ HiddenOnTaskbar: true, }, KeyBindings: map[string]func(window application.Window){ "F12": func(window application.Window) { systemTray.OpenMenu() }, }, })

各选项的用途(均可在 webview_window_options.go 找到定义):

选项作用
Frameless: true无边框窗口,贴合托盘弹窗的轻量观感
AlwaysOnTop: true置顶,保证弹出时不被其他窗口遮挡
Hidden: true初始隐藏,等待托盘触发
DisableResize: true禁止拖拽改变大小
HideOnEscape: true按 Esc 隐藏窗口
HideOnFocusLost: true失焦自动隐藏,实现"点击外部即关闭"
Windows.HiddenOnTaskbar: trueWindows 下隐藏任务栏条目
KeyBindings注册全局按键 F12,触发systemTray.OpenMenu()

两个隐藏选项值得展开:

  • HideOnFocusLost源码注释明确指出(webview_window_options.go):它"对弹出/临时窗口(如托盘附属窗口)非常有用"。但在Linux 的 focus-follows-mouse 窗口管理器(如 Hyprland、Sway、i3)上会被自动禁用,因为鼠标一移开窗口就隐藏,体验反而变差(实现见 webview_window_linux.go 的detectFocusFollowsMouse逻辑)。
  • HideOnEscape同样适合弹出式窗口:用户按 Esc 即可快速收拢。

第四步:绑定关闭事件与交互

window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { window.Hide() e.Cancel() })

这里拦截了窗口关闭事件:将"关闭"转译为"隐藏"并调用e.Cancel()取消真正的销毁。对托盘应用这是关键设计——用户点击窗口关闭按钮后应用不会退出,托盘继续常驻。

macOS 下设置模板图标(Template Icon):

if runtime.GOOS == "darwin" { systemTray.SetTemplateIcon(icons.SystrayMacTemplate) }

icons.SystrayMacTemplate由 v3/pkg/icons/icons.go 通过//go:embed DefaultMacTemplateIcon.png内嵌。模板图标是 macOS 的特殊机制:图标自带透明通道,系统根据菜单栏的明暗模式自动渲染为黑白样式,避免出现"深色模式下图标看不清"的问题。

第五步:将窗口附着到托盘

systemTray.AttachWindow(window).WindowOffset(5) err := app.Run() if err != nil { log.Fatal(err) }

AttachWindow把窗口"绑"到托盘图标上(systemtray.go):

  • AttachWindow(window):记录关联窗口,图标被点击时自动切换窗口显隐;
  • WindowOffset(5):设置托盘图标与窗口之间的像素间距为 5px;
  • 可选链式调用WindowDebounce(d)调整 Windows 点击防抖间隔。

点击交互背后的智能默认值

示例代码并未显式编写"单击切换窗口"的逻辑——它来自applySmartDefaults(systemtray.go):

func (s *SystemTray) applySmartDefaults() { hasWindow := s.attachedWindow.Window != nil hasMenu := s.menu != nil if s.clickHandler == nil && hasWindow { s.clickHandler = s.ToggleWindow } if s.rightClickHandler == nil && hasMenu { s.rightClickHandler = s.ShowMenu } }

规则:

  • 若关联了窗口且未设置单击回调 →左键单击切换窗口显隐
  • 若设置了菜单且未设置右键回调 →右键单击弹出菜单

因此只要AttachWindow了窗口,即使不写任何OnClick,示例行为(单击切换)也自然成立。ToggleWindow的实现(systemtray.go)会先读取窗口初始可见性,再调用PositionWindow将窗口定位到图标附近后Show().Focus()

如果需要自定义行为,可直接覆盖回调:

systemTray.OnClick(func() { /* 自定义单击逻辑 */ }) systemTray.OnRightClick(func() { /* 自定义右键逻辑 */ }) systemTray.OnDoubleClick(func() { /* 双击 */ }) systemTray.OnMouseEnter(func() { /* 鼠标进入 */ }) systemTray.OnMouseLeave(func() { /* 鼠标离开 */ })

常用 API 速查

以下方法均返回*SystemTray支持链式调用(定义见 systemtray.go):

方法作用
SetIcon(icon []byte)设置普通图标
SetTemplateIcon(icon []byte)设置 macOS 模板图标(自适应明暗)
SetDarkModeIcon(icon []byte)设置深色模式图标
SetLabel(label string)设置托盘文本标签
SetTooltip(tooltip string)设置悬停提示
SetMenu(menu *Menu)关联弹出菜单
SetIconPosition(pos IconPosition)设置图标与标签的相对位置(macOS)
AttachWindow(window)/WindowOffset(n)/WindowDebounce(d)附着窗口及微调
OnClick / OnRightClick / OnDoubleClick / ...注册交互回调
Show() / Hide() / Destroy()显隐托盘、销毁托盘

OpenMenu()要求必须先SetMenu,否则直接返回(systemtray.go)。

进阶:给托盘加一个右键菜单

示例只演示了单击切换,托盘应用最常见的形态还包含右键菜单。参考applySmartDefaults的规则,只需把菜单与托盘关联即可:

menu := app.NewMenu() menu.Add("显示窗口").OnClick(func(ctx *application.Context) { systemTray.ShowWindow() }) menu.Add("隐藏窗口").OnClick(func(ctx *application.Context) { systemTray.HideWindow() }) menu.AddSeparator() menu.Add("退出").OnClick(func(ctx *application.Context) { app.Quit() }) systemTray.SetMenu(menu)

ShowWindow()/HideWindow()是托盘对附属窗口的显隐快捷入口(systemtray.go),app.Quit()结束事件循环。

平台差异与注意事项

  1. macOS:务必使用ActivationPolicyAccessory+ 模板图标,否则应用会占据 Dock 且图标在深色菜单栏下不可见。
  2. WindowsHiddenOnTaskbar让窗口不占用任务栏;Debounce(默认 200ms)避免单击托盘图标时窗口"闪一下又消失";图标在通知区域弹出层时窗口会出现在右下角。
  3. Linux:README 未标注状态,且HideOnFocusLost在 focus-follows-mouse 的 WM 下会被自动禁用(见 webview_window_linux.go);不同桌面环境的托盘实现差异较大,建议在目标发行版上实测。
  4. 关闭即隐藏:通过WindowClosing钩子 +e.Cancel()保证关闭窗口不退出进程,这是托盘应用存活的关键。

总结

systray-basic用约 60 行代码串起了 Wails v3 托盘应用的全部核心链路:SystemTrayManager.New()创建托盘 →AttachWindow+ 智能默认值实现单击切换 → 窗口选项配合实现失焦/ Esc 隐藏 →WindowClosing钩子阻止退出。基于这个模板,替换图标资源与前端 Assets、增加右键菜单与自定义事件,即可快速扩展出自己的常驻托盘工具。

【免费下载链接】wailsCreate beautiful applications using Go项目地址: https://gitcode.com/gh_mirrors/wa/wails

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询