Electron Tray API 知识点总结
目录
- [1. 概述](#1. 概述 "#1-%E6%A6%82%E8%BF%B0")
- [2. 创建托盘](#2. 创建托盘 "#2-%E5%88%9B%E5%BB%BA%E6%89%98%E7%9B%98")
- [3. 实例事件](#3. 实例事件 "#3-%E5%AE%9E%E4%BE%8B%E4%BA%8B%E4%BB%B6")
- [4. 实例方法](#4. 实例方法 "#4-%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95")
- [5. 托盘菜单](#5. 托盘菜单 "#5-%E6%89%98%E7%9B%98%E8%8F%9C%E5%8D%95")
- [6. 托盘气泡通知](#6. 托盘气泡通知 "#6-%E6%89%98%E7%9B%98%E6%B0%94%E6%B3%A1%E9%80%9A%E7%9F%A5")
- [7. 实际应用示例](#7. 实际应用示例 "#7-%E5%AE%9E%E9%99%85%E5%BA%94%E7%94%A8%E7%A4%BA%E4%BE%8B")
- [8. 平台差异](#8. 平台差异 "#8-%E5%B9%B3%E5%8F%B0%E5%B7%AE%E5%BC%82")
1. 概述
什么是系统托盘?
系统托盘是位于屏幕底部(Windows/Linux)或顶部菜单栏右侧(macOS)的常驻图标。
复制代码
┌─────────────────────────────────────────────────────────────┐
│ Windows/Linux │
│ ┌──────┐ ┌──────────────────────────────────────────┐ │
│ │ 📁 │ │ 应用程序区域 │ │
│ └──────┘ └──────────────────────────────────────────┘ │
│ ↑ │
│ 系统托盘 │
├─────────────────────────────────────────────────────────────┤
│ macOS │
│ ┌──────────────────────────────┐ ┌────┐ ┌────┐ │
│ │ 应用程序菜单 │ │ 📁 │ │ 🔊 │ ← 托盘 │
│ └──────────────────────────────┘ └────┘ └────┘ │
└─────────────────────────────────────────────────────────────┘
Tray 模块作用
- 在系统托盘区域显示图标
- 提供右键上下文菜单
- 处理点击事件(单击、双击、右键)
- 显示气泡通知(Windows)
- 支持拖拽操作(macOS)
2. 创建托盘
2.1 基本创建
javascript
复制代码
const { app, Tray, Menu } = require('electron')
let tray = null
app.whenReady().then(() => {
tray = new Tray('/path/to/icon.png')
// 设置提示文本
tray.setToolTip('我的应用')
// 设置右键菜单
const contextMenu = Menu.buildFromTemplate([
{ label: '显示窗口', click: () => mainWindow.show() },
{ label: '隐藏窗口', click: () => mainWindow.hide() },
{ type: 'separator' },
{ label: '退出', click: () => app.quit() }
])
tray.setContextMenu(contextMenu)
})
2.2 构造函数参数
javascript
复制代码
new Tray(image, [guid])
| 参数 |
类型 |
说明 |
平台 |
image |
NativeImage/string |
托盘图标 |
全部 |
guid |
string |
唯一标识符 |
Windows/macOS |
2.3 图标要求
| 平台 |
推荐格式 |
尺寸要求 |
| Windows |
ICO |
16x16, 32x32 等多尺寸 |
| macOS |
Template Image |
16x16 @1x, 32x32 @2x (144dpi) |
| Linux |
PNG |
16x16 或 22x22 |
2.4 macOS 模板图片
javascript
复制代码
const { nativeImage } = require('electron')
// 创建模板图片(白色/透明,适合深色/浅色模式)
const icon = nativeImage.createFromPath('icon.png')
icon.setTemplateImage(true)
const tray = new Tray(icon)
3. 实例事件
3.1 鼠标点击事件
| 事件 |
触发条件 |
平台 |
click |
单击图标 |
全部 |
right-click |
右键点击 |
macOS/Windows |
double-click |
双击图标 |
macOS/Windows |
middle-click |
中键点击 |
Windows |
javascript
复制代码
tray.on('click', (event, bounds) => {
// bounds: { x, y, width, height }
mainWindow.show()
})
tray.on('right-click', (event, bounds) => {
// 显示自定义菜单
tray.popUpContextMenu(customMenu)
})
tray.on('double-click', () => {
// 打开主窗口
mainWindow.show()
mainWindow.focus()
})
3.2 鼠标移动事件
| 事件 |
触发条件 |
平台 |
mouse-enter |
鼠标进入图标区域 |
macOS/Windows |
mouse-leave |
鼠标离开图标区域 |
macOS/Windows |
mouse-move |
鼠标在图标上移动 |
macOS/Windows |
mouse-down |
鼠标按下 |
macOS |
mouse-up |
鼠标释放 |
macOS |
javascript
复制代码
tray.on('mouse-enter', () => {
// 显示提示或预览
})
tray.on('mouse-leave', () => {
// 隐藏预览
})
3.3 拖拽事件 (macOS)
| 事件 |
触发条件 |
drop |
有拖拽项进入图标区域 |
drop-files |
文件拖入图标 |
drop-text |
文本拖入图标 |
drag-enter |
拖拽进入 |
drag-leave |
拖拽离开 |
drag-end |
拖拽结束 |
javascript
复制代码
tray.on('drop-files', (event, files) => {
console.log('拖放的文件:', files)
files.forEach(file => processFile(file))
})
tray.on('drop-text', (event, text) => {
console.log('拖放的文本:', text)
})
3.4 气泡通知事件 (Windows)
| 事件 |
触发条件 |
balloon-show |
气泡显示 |
balloon-click |
气泡被点击 |
balloon-closed |
气泡关闭 |
4. 实例方法
4.1 图标操作
javascript
复制代码
// 设置图标
tray.setImage(icon)
// macOS:设置按下时的图标
tray.setPressedImage(icon)
// 获取图标是否已销毁
tray.isDestroyed() // boolean
// 销毁托盘图标
tray.destroy()
4.2 提示文本
javascript
复制代码
// 设置悬停提示
tray.setToolTip('这是我的应用程序')
// 获取提示文本
const tooltip = tray.getToolTip()
4.3 macOS 特有方法
javascript
复制代码
// 设置标题
tray.setTitle('App Name')
// 获取标题
const title = tray.getTitle()
// 忽略双击事件(用于区分单击和双击)
tray.setIgnoreDoubleClickEvents(true)
const isIgnored = tray.getIgnoreDoubleClickEvents()
// 获取托盘边界
const bounds = tray.getBounds()
// bounds: { x: number, y: number, width: number, height: number }
4.4 菜单操作
javascript
复制代码
// 设置右键菜单
tray.setContextMenu(menu)
// 弹出右键菜单
tray.popUpContextMenu([menu, position])
// 关闭右键菜单
tray.closeContextMenu()
4.5 Windows 气泡通知
javascript
复制代码
// 显示气泡通知
tray.displayBalloon({
iconType: 'info', // none, info, warning, error, custom
title: '新消息',
content: '您有一条新通知',
largeIcon: true, // 使用大图标
noSound: false, // 不播放声音
respectQuietTime: false // 休息时间不显示
})
// 移除气泡
tray.removeBalloon()
// 聚焦到托盘
tray.focus()
5. 托盘菜单
5.1 基本菜单
javascript
复制代码
const contextMenu = Menu.buildFromTemplate([
{ label: '显示主窗口', click: () => showWindow() },
{ label: '设置', click: () => openSettings() },
{ type: 'separator' },
{ label: '关于', click: () => showAbout() },
{ type: 'separator' },
{ label: '退出', click: () => app.quit() }
])
tray.setContextMenu(contextMenu)
5.2 动态菜单
javascript
复制代码
// 创建菜单模板
function buildContextMenu() {
return Menu.buildFromTemplate([
{
label: '通知',
submenu: [
{ label: '启用通知', type: 'checkbox', checked: true },
{ label: '静默模式', type: 'checkbox', checked: false }
]
},
{ type: 'separator' },
{
label: `状态: ${isRunning ? '运行中' : '已停止'}`,
enabled: false
},
{ type: 'separator' },
{ label: '退出', click: () => app.quit() }
])
}
// Linux 需要重新设置菜单才能生效
tray.setContextMenu(buildContextMenu())
5.3 根据状态动态切换图标
javascript
复制代码
function updateTrayIcon(isActive) {
const icon = isActive ? 'active.png' : 'inactive.png'
tray.setImage(icon)
tray.setToolTip(isActive ? '应用运行中' : '应用已停止')
}
6. 托盘气泡通知
6.1 Windows 气泡通知
javascript
复制代码
// 显示气泡
tray.displayBalloon({
iconType: 'info',
title: '下载完成',
content: '文件已成功下载到 Downloads 文件夹'
})
// 带图标的气泡
tray.displayBalloon({
icon: path.join(__dirname, 'custom-icon.png'),
iconType: 'custom',
title: '自定义通知',
content: '这是一条自定义通知'
})
6.2 监听气泡事件
javascript
复制代码
tray.on('balloon-click', () => {
// 点击气泡后打开相关窗口
mainWindow.show()
mainWindow.focus()
})
tray.on('balloon-closed', () => {
console.log('气泡通知已关闭')
})
7. 实际应用示例
7.1 完整的托盘应用
javascript
复制代码
const { app, Tray, Menu, nativeImage, BrowserWindow } = require('electron')
let tray = null
let mainWindow = null
function createTray() {
// 创建托盘图标
const icon = nativeImage.createFromPath('icon.png')
tray = new Tray(icon.resize({ width: 16 }))
// 设置提示
tray.setToolTip('我的 Electron 应用')
// 创建菜单
const contextMenu = Menu.buildFromTemplate([
{
label: '显示窗口',
click: () => {
if (mainWindow) {
mainWindow.show()
mainWindow.focus()
}
}
},
{
label: '隐藏窗口',
click: () => {
if (mainWindow) {
mainWindow.hide()
}
}
},
{ type: 'separator' },
{
label: '运行状态',
submenu: [
{ label: '● 运行中', type: 'radio', checked: true },
{ label: '○ 已暂停', type: 'radio', checked: false }
]
},
{ type: 'separator' },
{
label: '退出',
click: () => {
app.quit()
}
}
])
tray.setContextMenu(contextMenu)
// 点击事件:显示/隐藏窗口
tray.on('click', () => {
if (mainWindow) {
if (mainWindow.isVisible()) {
mainWindow.hide()
} else {
mainWindow.show()
mainWindow.focus()
}
}
})
// 右键事件:显示菜单
tray.on('right-click', () => {
tray.popUpContextMenu(contextMenu)
})
}
function showNotification(message) {
// Windows 气泡通知
if (process.platform === 'win32') {
tray.displayBalloon({
iconType: 'info',
title: '通知',
content: message
})
}
}
// 创建窗口
app.whenReady().then(() => {
mainWindow = new BrowserWindow()
mainWindow.loadURL('https://example.com')
createTray()
})
// 关闭窗口时隐藏(不退出应用)
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault()
mainWindow.hide()
}
})
// 应用退出时清理
app.on('before-quit', () => {
app.isQuitting = true
})
7.2 音乐播放器托盘
javascript
复制代码
class MusicPlayerTray {
constructor(player) {
this.player = player
this.tray = null
this.init()
}
init() {
this.tray = new Tray(this.getIcon())
this.tray.setToolTip('音乐播放器')
this.updateMenu()
this.tray.on('click', () => this.togglePlay())
}
getIcon() {
return this.player.isPlaying ? 'playing.png' : 'paused.png'
}
updateMenu() {
const menu = Menu.buildFromTemplate([
{
label: this.player.isPlaying ? '⏸ 暂停' : '▶ 播放',
click: () => this.togglePlay()
},
{ type: 'separator' },
{
label: '上一首',
click: () => this.player.previous()
},
{
label: '下一首',
click: () => this.player.next()
},
{ type: 'separator' },
{
label: `正在播放: ${this.player.currentSong}`,
enabled: false
},
{ type: 'separator' },
{
label: '退出',
click: () => app.quit()
}
])
this.tray.setContextMenu(menu)
this.tray.setImage(this.getIcon())
}
togglePlay() {
this.player.isPlaying ? this.player.pause() : this.player.play()
this.updateMenu()
}
}
7.3 下载管理器托盘
javascript
复制代码
class DownloadManagerTray {
constructor(downloadManager) {
this.downloadManager = downloadManager
this.tray = null
this.init()
}
init() {
this.tray = new Tray('download-icon.png')
this.updateTray()
// macOS: 拖拽文件下载
this.tray.on('drop-files', (event, files) => {
files.forEach(file => this.downloadManager.add(file))
})
}
updateTray() {
const active = this.downloadManager.getActiveDownloads()
// 根据下载数量显示不同提示
const tooltip = active.length > 0
? `正在下载 ${active.length} 个文件`
: '没有正在下载'
this.tray.setToolTip(tooltip)
// 更新菜单
const menu = Menu.buildFromTemplate([
{
label: `下载中 (${active.length})`,
submenu: active.map(d => ({
label: `${d.name} - ${d.progress}%`,
enabled: false
}))
},
{ type: 'separator' },
{
label: '全部暂停',
click: () => this.downloadManager.pauseAll(),
enabled: active.length > 0
},
{
label: '全部取消',
click: () => this.downloadManager.cancelAll(),
enabled: active.length > 0
},
{ type: 'separator' },
{
label: '打开下载文件夹',
click: () => shell.openPath(app.getPath('downloads'))
},
{ type: 'separator' },
{
label: '退出',
click: () => app.quit()
}
])
this.tray.setContextMenu(menu)
}
}
8. 平台差异
8.1 功能支持
| 功能 |
macOS |
Windows |
Linux |
| 单击事件 |
✅ |
✅ |
⚠️ |
| 右键菜单 |
✅ |
✅ |
✅ |
| 双击事件 |
✅ |
✅ |
❌ |
| 气泡通知 |
❌ |
✅ |
❌ |
| 拖拽支持 |
✅ |
❌ |
❌ |
| 标题显示 |
✅ |
❌ |
❌ |
| 模板图片 |
✅ |
❌ |
❌ |
| GUID 持久化 |
✅ |
✅ |
❌ |
8.2 图标要求
| 平台 |
推荐格式 |
尺寸 |
备注 |
| Windows |
ICO |
16x16, 32x32 |
多尺寸 ICO 最佳 |
| macOS |
PNG (模板) |
16x16 @1x, 32x32 @2x |
白色透明图 |
| Linux |
PNG |
22x22 |
取决于桌面环境 |
8.3 macOS 模板图片注意事项
css
复制代码
✅ 正确:
icon.png (16x16)
icon@2x.png (32x32, 144dpi)
文件名包含 "Template" 或设置 setTemplateImage(true)
❌ 错误:
icon-hashed.png (文件名被 webpack 哈希)
icon@3x.png (不支持的分辨率)
8.4 Linux 特殊处理
javascript
复制代码
// Linux 修改菜单项后需要重新设置
const contextMenu = Menu.buildFromTemplate([...])
tray.setContextMenu(contextMenu)
// 修改某个菜单项
contextMenu.items[0].checked = false
// 必须再次调用才能生效
tray.setContextMenu(contextMenu)
8.5 Windows GUID
javascript
复制代码
// Windows 使用 GUID 保持托盘位置
const tray = new Tray(icon, {
guid: 'my-app-tray-uuid' // 建议使用 UUID 格式
})
// macOS 也可以使用 guid
const tray = new Tray(icon, {
guid: 'my-app-tray-uuid' // 保持托盘位置
})
最佳实践
✅ 推荐做法
javascript
复制代码
// 1. 应用退出时清理托盘
app.on('before-quit', () => {
app.isQuitting = true
if (tray) {
tray.destroy()
}
})
// 2. 窗口关闭时隐藏而非退出
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault()
mainWindow.hide()
}
})
// 3. 使用原生图片
const icon = nativeImage.createFromPath('icon.png')
// 4. macOS 使用模板图片
icon.setTemplateImage(true)
// 5. 正确处理平台差异
if (process.platform === 'win32') {
tray.displayBalloon({ ... })
}
❌ 避免做法
javascript
复制代码
// 1. 不要忘记处理窗口关闭事件
mainWindow.on('close', (event) => {
// 防止用户直接关闭窗口导致应用退出
event.preventDefault()
mainWindow.hide()
})
// 2. macOS 不要用彩色图标
// 应该用白色透明图作为模板图片
// 3. 不要忘记销毁托盘
app.on('quit', () => {
if (tray) {
tray.destroy()
}
})
事件速查表
点击事件
arduino
复制代码
click 单击(全部平台)
right-click 右键(macOS/Windows)
double-click 双击(macOS/Windows)
middle-click 中键(Windows)
鼠标事件
arduino
复制代码
mouse-enter 进入图标区域(macOS/Windows)
mouse-leave 离开图标区域(macOS/Windows)
mouse-move 鼠标移动(macOS/Windows)
mouse-down 鼠标按下(macOS)
mouse-up 鼠标释放(macOS)
拖拽事件 (macOS)
sql
复制代码
drop 拖拽进入
drop-files 拖拽文件
drop-text 拖拽文本
drag-enter 拖入
drag-leave 拖出
drag-end 拖拽结束
气泡事件 (Windows)
arduino
复制代码
balloon-show 气泡显示
balloon-click 气泡点击
balloon-closed 气泡关闭
方法速查表
基础
scss
复制代码
new Tray(image, [guid]) 创建托盘
tray.destroy() 销毁托盘
tray.isDestroyed() 检查是否已销毁
外观
scss
复制代码
tray.setImage(image) 设置图标
tray.setToolTip(text) 设置提示
tray.setTitle(title) 设置标题(macOS)
tray.getTitle() 获取标题(macOS)
菜单
scss
复制代码
tray.setContextMenu(menu) 设置右键菜单
tray.popUpContextMenu([menu]) 弹出菜单
tray.closeContextMenu() 关闭菜单
气泡 (Windows)
scss
复制代码
tray.displayBalloon(options) 显示气泡
tray.removeBalloon() 移除气泡
tray.focus() 聚焦托盘
文档基于 Electron v28+ Tray API 编写