人人都会AI编程

7.3 系统托盘、菜单与快捷键

更新时间:2026-07-11

系统托盘、应用菜单和全局快捷键是让 Electron 应用真正“融入”桌面环境的三大核心能力。它们都由主进程控制,共同决定了用户如何快速唤出、操作和感知你的应用。本章会通过实际可用的代码片段,逐一讲解这三大模块。


7.3.1 系统托盘

系统托盘(Tray)是应用在桌面环境中的常驻入口,通常显示在任务栏右下角 (Windows) 或菜单栏右侧 (macOS)。即使窗口全部关闭,托盘图标依然保留,点击可以唤出菜单或窗口。

创建托盘图标
app.whenReady() 中创建,并且需要传入一个图标文件。推荐使用 16x16 或 22x22 的 png,macOS 还支持 Template 图片以适配深色/浅色菜单栏。

const { app, Tray, Menu, BrowserWindow } = require('electron');
const path = require('path');

let tray = null;

function createTray() {
  // 图标路径需根据平台调整
  const iconPath = path.join(__dirname, 'assets', 'tray-icon.png');
  tray = new Tray(iconPath);

  // 托盘提示文字
  tray.setToolTip('我的 Electron 应用');

  // 右键菜单(或 macOS 点击菜单)
  const contextMenu = Menu.buildFromTemplate([
    {
      label: '显示主窗口',
      click: () => {
        const win = BrowserWindow.getAllWindows()[0];
        if (win) {
          win.show();
          win.focus();
        }
      }
    },
    { type: 'separator' },
    {
      label: '退出',
      click: () => {
        app.quit();
      }
    }
  ]);

  tray.setContextMenu(contextMenu);

  // 托盘图标被单击时的行为(macOS 习惯单击显示/隐藏窗口)
  tray.on('click', () => {
    const win = BrowserWindow.getAllWindows()[0];
    if (win) {
      win.isVisible() ? win.hide() : win.show();
    }
  });
}

app.whenReady().then(() => {
  createTray();
  // ... 创建窗口
});

重要注意事项

  • 托盘图标在 macOS 上如果没有右键菜单,会在 Dock 中一直显示。要隐藏 Dock 的图标,需设置 app.dock.hide()(仅在 macOS 有效)。
  • 应用退出前需销毁托盘对象,否则可能导致进程残留。可以在 app.on('will-quit') 中调用 tray.destroy()
  • 动态托盘图标(如显示未读数字、状态变化)可通过 tray.setImage(path)tray.setTitle('文本') 实现,后者在 Windows 上显示为图标旁的 Badge。

真实应用如 Slack、微信桌面版,都通过托盘图标让用户随时呼出窗口或查看状态,这是后台常驻类应用的标配。


7.3.2 应用菜单

Electron 中的菜单分为 应用菜单(窗口菜单栏)和 右键上下文菜单,都由 Menu 模块构建。应用菜单在 macOS 中显示为屏幕顶部的全局菜单,在 Windows/Linux 中则附加在窗口内部(除非使用无框窗口)。

创建基础菜单
在主进程中使用 Menu.buildFromTemplate 定义菜单模板,然后通过 Menu.setApplicationMenu(menu) 挂载。

const { app, Menu } = require('electron');

function createMenu() {
  const template = [
    {
      label: '文件',
      submenu: [
        {
          label: '新建',
          accelerator: 'CmdOrCtrl+N',
          click: () => {
            // 发送事件到渲染进程或新建窗口
          }
        },
        { type: 'separator' },
        { label: '退出', role: 'quit' }
      ]
    },
    {
      label: '编辑',
      submenu: [
        { label: '撤销', role: 'undo' },
        { label: '重做', role: 'redo' },
        { type: 'separator' },
        { label: '剪切', role: 'cut' },
        { label: '复制', role: 'copy' },
        { label: '粘贴', role: 'paste' }
      ]
    },
    {
      label: '帮助',
      submenu: [
        {
          label: '关于',
          click: () => {
            // 显示关于对话框
          }
        }
      ]
    }
  ];

  // macOS 需要特别处理:第一个菜单应为应用名
  if (process.platform === 'darwin') {
    template.unshift({
      label: app.getName(),
      submenu: [
        { role: 'about' },
        { type: 'separator' },
        { role: 'services' },
        { type: 'separator' },
        { role: 'hide' },
        { role: 'hideOthers' },
        { role: 'unhide' },
        { type: 'separator' },
        { role: 'quit' }
      ]
    });
  }

  const menu = Menu.buildFromTemplate(template);
  Menu.setApplicationMenu(menu);
}

app.whenReady().then(() => {
  createMenu();
  // ... 创建窗口
});

常用 role 快速定义
为减少重复代码,Electron 预定义了多个标准操作角色,例如 'copy', 'paste', 'undo', 'selectAll' 等。直接使用 role 会自动绑定系统原生行为,并自动适配 macOS 的特殊处理(如“复制”会自动映射到 command+c)。在模板中,role 可以替代手写的 click

动态菜单
根据应用状态改变菜单项(如“禁用”某项),只需重新生成菜单并调用 Menu.setApplicationMenu 即可。频繁更新的场景可以使用封装函数,由渲染进程通过 IPC 通知主进程刷新菜单。

上下文菜单
对页面内特定元素(如右键点击)弹出的菜单,通常由渲染进程捕获右键事件,通过 IPC 请求主进程显示 Menu.popup(),或使用 webContents.on('context-menu') 事件统一拦截。

// 主进程中监听渲染进程的右键事件
win.webContents.on('context-menu', (event, params) => {
  const menu = Menu.buildFromTemplate([
    { label: '复制', role: 'copy' },
    { label: '粘贴', role: 'paste' },
    { type: 'separator' },
    { label: '检查元素', click: () => win.webContents.openDevTools() }
  ]);
  menu.popup({ window: win, x: params.x, y: params.y });
});

许多编辑器型应用(如 VS Code、Typora)都在右键菜单中集成了格式化、代码折叠等功能,这正是 context-menu 事件的典型应用。


7.3.3 全局快捷键

全局快捷键(Global Shortcuts)允许用户即使应用处于后台或最小化,也能通过键盘组合键触发特定行为(如截图、显示/隐藏窗口)。Electron 使用 globalShortcut 模块在主进程中注册。

注册与注销
globalShortcut.register 接收一个 Accelerator 字符串(如 'CommandOrControl+Shift+K')和回调函数。成功注册返回 true,若已被系统其他应用占用则返回 false

const { globalShortcut, BrowserWindow } = require('electron');

function registerShortcuts() {
  const ret = globalShortcut.register('CmdOrCtrl+Shift+K', () => {
    // 切换主窗口可见性
    const win = BrowserWindow.getAllWindows()[0];
    if (win) {
      win.isVisible() ? win.hide() : win.show();
    }
  });

  if (!ret) {
    console.warn('快捷键注册失败,可能是被其他应用占用');
  }
}

app.whenReady().then(() => {
  registerShortcuts();
  // ...
});

// 应用失去焦点或退出前务必清理
app.on('will-quit', () => {
  globalShortcut.unregisterAll();
});

安全与用户体验考量

  • 全局快捷键是系统级资源,注册失败的常见原因是与其他软件冲突(如微信、QQ 等默认截图键)。应妥善处理注册失败情形,可能需要在界面中提示用户修改。
  • 不要滥用全局快捷键,避免与用户常用系统快捷键冲突。建议提供设置页面让用户自定义组合键。
  • 在 macOS App Store 发布的沙盒应用中,全局快捷键可能受到限制,需额外声明权限。

与渲染进程的交互
全局快捷键的回调运行在主进程,如需触发界面操作(如截图后保存图片),通常通过主进程向渲染进程发送消息。可以使用 win.webContents.send('shortcut-triggered', 'action') 配合 ipcRenderer.on 实现。


7.3.4 三者协同的实际场景

一个典型的“剪贴板管理工具”会这样组合使用:

  1. 系统托盘:程序启动后最小化到托盘,图标悬浮显示当前剪切板条目数。
  2. 全局快捷键:用户按下 CmdOrCtrl+Shift+V 时,弹出窗口并自动聚焦到搜索框。
  3. 右键菜单:在条目上右键提供“复制”、“删除”、“固定到顶部”等选项。
  4. 应用菜单:提供“历史记录”、“设置”、“帮助”等下拉菜单,其中“设置”可以打开快捷键自定义页面。

这种设计让用户无需频繁打开窗口,又不失完整的桌面操作体验,是高级 Electron 应用的必备模式。


通过本章学习,你应该能够为主进程添加一个带菜单的系统托盘图标,并利用全局快捷键让应用呼之即来。下一节我们将深入应用打包与更新的实战,让这些功能最终到达用户桌面。