Electron Tray API 详解:托盘图标、右键菜单、气泡通知

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 编写

相关推荐
七十二時_阿川4 小时前
Electron 多显示器开发?这篇帮你搞定屏幕坐标与窗口定位!
前端·electron
番茄炒韭菜4 小时前
windows10下安装mise
前端
用户938515635074 小时前
AI全栈前端实战|DeepSeek + CC插件,1小时产出高质量外卖App落地页
前端
AI2中文网4 小时前
App Inventor 2 向心力实验App - 探究向心力F与角速度ω、半径r、质量m的关系
前端·javascript·r语言
程序软件分享5 小时前
vue多语言交易所系统/期货/合约交易/质押生息/盲盒/挖矿/跟单源码
前端·javascript·vue.js·期货平台源码
悟空瞎说5 小时前
【前端视角学 Rust】1.3 一文吃透 Cargo:Rust 的 npm+webpack,新手必懂工程化工具
前端
yingyima5 小时前
Linux Crontab 速查手册:5 个问题直击核心语法与常用场景
前端
用户4445543654265 小时前
Android compose
前端
龙骑utr5 小时前
经典「Pin + 横向滚动」效果
前端·动效