Windows下GoLang Fyne窗口关闭与系统托盘应用兼容问题
解决Fyne+systray托盘应用关闭窗口崩溃问题
问题根源
崩溃的核心原因是调用mainApp.Quit()会终止Fyne的GLFW驱动事件循环,内部通道被永久关闭,后续再次调用mainApp.Run()时就会触发panic: close of closed channel。而循环重建App会导致GLFW初始化错误,因为GLFW要求在主线程完成初始化,且不支持重复初始化。
解决方案
- 放弃调用
mainApp.Quit(),改为隐藏窗口(window.Hide()),保持Fyne事件循环持续运行 - 重写窗口的关闭拦截器(
SetCloseIntercept),让点击红色×时执行隐藏操作而非退出App - 调整主线程逻辑:Fyne的
Run()会阻塞主线程运行事件循环,托盘逻辑通过goroutine处理即可,无需循环调用Run()
修改后的完整代码
package main import ( icon "company/winres" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" "github.com/getlantern/systray" "github.com/k0kubun/pp/v3" ) // 用于通知显示窗口的通道(空结构体仅做信号传递) var showWindowCh = make(chan struct{}) var mainApp fyne.App var mainWindow fyne.Window func main() { // 初始化pp日志配色 scheme := pp.ColorScheme{ Integer: pp.Yellow | pp.Bold, String: pp.Green | pp.BackgroundBlack | pp.Bold, StringQuotation: pp.Green | pp.BackgroundBlack | pp.Bold, } pp.Default.SetColorScheme(scheme) pp.Println("Starting Application now ...") // 启动系统托盘(放入goroutine避免阻塞主线程) go func() { systray.Register(onReady, onExit) }() // 初始化Fyne应用和窗口 pp.Println("Creating app and window") mainApp = app.New() mainWindow = mainApp.NewWindow("Backup Information") mainWindow.Resize(fyne.NewSize(256, 256)) // 拦截窗口关闭事件:点击×时隐藏窗口而非退出App mainWindow.SetCloseIntercept(func() { mainWindow.Hide() }) // 构建窗口内容 yesBtn := widget.NewButton("Yes", func() { mainWindow.Hide() }) yesBtn.Importance = widget.HighImportance mainWindow.SetContent(container.NewVBox( widget.NewLabel("Are you sure you want to interact with this test dialog?"), container.NewHBox( layout.NewSpacer(), widget.NewButton("No", func() { mainWindow.Hide() }), yesBtn, layout.NewSpacer(), ), )) // 监听显示窗口的信号 go func() { for range showWindowCh { mainWindow.Show() } }() // 运行Fyne事件循环(阻塞主线程,保持应用持续运行) mainApp.Run() } func onReady() { // 设置托盘图标和标题 systray.SetIcon(icon.Data) systray.SetTemplateIcon(icon.Data, icon.Data) systray.SetTitle("asdf") // 创建托盘菜单 mInformation := systray.AddMenuItem("Info", "Information") mBackupNow := systray.AddMenuItem("Now", "Immediately start") mQuit := systray.AddMenuItem("Quit", "Quit") // 处理菜单点击事件 go func() { for { select { case <-mInformation.ClickedCh: pp.Println("User chose to view information") showWindowCh <- struct{}{} case <-mBackupNow.ClickedCh: pp.Println("User chose to start backup") case <-mQuit.ClickedCh: // 退出时同时关闭Fyne应用和托盘 mainApp.Quit() systray.Quit() } } }() } func onExit() { pp.Println("Exiting now ...") }
关键修改点
- 将原字符串通道改为空结构体通道
showWindowCh,更适合仅做信号传递的场景 - 给窗口添加
SetCloseIntercept,覆盖默认的关闭行为为隐藏窗口 - 所有按钮的关闭回调改为
mainWindow.Hide(),不再触发App退出 - 将
systray.Register放入goroutine运行,避免阻塞Fyne的主线程初始化 - 移除main函数末尾的循环
mainApp.Run(),因为mainApp.Run()本身会阻塞主线程维持事件循环
内容的提问来源于stack exchange,提问作者philbot999
相关产品推荐
相关产品推荐

