菜单是桌面应用最基本的交互组件之一。Electron 通过 Menu 模块提供了完整的原生菜单能力,包括窗口顶部的应用菜单和通过右键触发的上下文菜单。掌握菜单的创建与使用,是让你的应用融入操作系统习惯的重要一步。
应用菜单(Application Menu)
应用菜单通常位于窗口顶部:在 Windows 和 Linux 上它直接嵌在窗口标题栏下方,在 macOS 上则显示在屏幕顶端系统菜单栏。Electron 使用模板(template)的方式定义菜单结构,这种方式直观且易于维护。
创建并设置应用菜单
菜单的核心是一个 JSON 数组,每一项代表一个菜单项,可以嵌套子菜单。最基本的结构如下:
const { app, Menu, BrowserWindow } = require('electron');
const template = [
{
label: '文件',
submenu: [
{ label: '新建', accelerator: 'CmdOrCtrl+N', click: () => { /* 新建操作 */ } },
{ label: '打开', accelerator: 'CmdOrCtrl+O', click: () => { /* 打开操作 */ } },
{ type: 'separator' },
{ role: 'quit', label: '退出' }
]
},
{
label: '编辑',
submenu: [
{ role: 'undo', label: '撤销' },
{ role: 'redo', label: '重做' },
{ type: 'separator' },
{ role: 'cut', label: '剪切' },
{ role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' }
]
},
{
label: '查看',
submenu: [
{ role: 'reload', label: '重新加载' },
{ role: 'toggleDevTools', label: '开发者工具' }
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
要点说明:
accelerator:设置快捷键,CmdOrCtrl会自动根据平台识别为 macOS 的Cmd或 Windows/Linux 的Ctrl。常用组合如CmdOrCtrl+S、Shift+CmdOrCtrl+I等。role:Electron 提供的内置角色,可以直接赋予菜单项预定义的行为,例如copy、paste、reload、toggleDevTools等。使用role的好处是自动启用本地化文本和与系统行为的完美兼容,特别建议在标准菜单项上使用。click:自定义处理函数,当用户点击菜单项时触发。函数的参数包含menuItem、browserWindow、event,你可以通过browserWindow与当前窗口交互。type:菜单项类型,可选值有normal、separator、submenu、checkbox、radio,默认是normal。分隔符可以用来视觉上分组。
平台差异化处理
macOS 的应用菜单有一些特殊约定:最左侧的第一个菜单项通常是应用名(如你的应用名称),里面包含“关于”、“偏好设置”、“服务”、“退出”等。我们可以利用条件判断和 role: 'appMenu' 来适配:
const isMac = process.platform === 'darwin';
const template = [
// macOS 专属应用菜单
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about', label: '关于' },
{ type: 'separator' },
{ role: 'services', label: '服务' },
{ type: 'separator' },
{ role: 'hide', label: '隐藏' },
{ role: 'hideOthers', label: '隐藏其他' },
{ role: 'unhide', label: '显示全部' },
{ type: 'separator' },
{ role: 'quit', label: '退出' }
]
}] : []),
// 文件、编辑等菜单...
];
这样的模板保证在 macOS 上拥有符合系统习惯的菜单,而在 Windows/Linux 上自动跳过。
注意: 当你通过 Menu.setApplicationMenu(menu) 设置菜单后,它会应用到应用的所有窗口。如果需要在某些窗口上显示不同的菜单,可以在创建 BrowserWindow 后使用 win.setMenu(menu) 单独设置,但此时整个应用菜单会被覆盖;更常见的做法是保持一个全局菜单,通过 click 回调获取当前焦点窗口来执行操作。
上下文菜单(Context Menu)
上下文菜单即右键菜单,用户期望在特定区域点击右键时弹出相关操作。Electron 中实现上下文菜单通常有两种方式:在渲染进程中通过 window.addEventListener('contextmenu', ...) 处理,或通过 IPC 让主进程集中管理。出于安全性和推荐的架构,我们应当避免在渲染进程中直接使用 remote 模块(已被废弃),转而使用预加载脚本暴露安全的接口。
主进程端弹出菜单
首先在主进程(或通过 IPC 调用的处理函数)中构建并弹出菜单。这里我们设计一个通用方法,通过 IPC 接收菜单模板并显示:
// main.js 主进程
const { ipcMain, Menu, BrowserWindow } = require('electron');
ipcMain.on('show-context-menu', (event, template) => {
const menu = Menu.buildFromTemplate(template);
menu.popup(BrowserWindow.fromWebContents(event.sender));
});
渲染进程触发
在渲染进程的某个元素上监听 contextmenu 事件,阻止默认行为,然后通过预加载脚本暴露的 API 发送自定义菜单模板给主进程。
预加载脚本暴露方法:
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
showContextMenu: (template) => ipcRenderer.send('show-context-menu', template)
});
渲染进程代码(HTML 或 JS):
// 在某个 DOM 元素上绑定
document.getElementById('editable-area').addEventListener('contextmenu', (e) => {
e.preventDefault();
// 可以基于选区、位置等动态生成菜单
const template = [
{ label: '剪切', role: 'cut' },
{ label: '复制', role: 'copy' },
{ label: '粘贴', role: 'paste' },
{ type: 'separator' },
{
label: '清空内容',
click: () => {
document.getElementById('editable-area').innerHTML = '';
}
}
];
window.electronAPI.showContextMenu(template);
});
关键细节:
Menu.popup()会自动在鼠标当前位置弹出菜单,也可以传入x, y坐标精确控制位置。- 如果菜单模板中某个菜单项的
click需要在渲染进程上下文中执行(比如修改 DOM),可以继续使用 IPC 回传信号,或者直接在click函数中通过webContents.executeJavaScript在渲染进程执行代码。但更简洁的做法是使用role处理标准操作,自定义逻辑则在菜单项的click中发送另一个 IPC 消息到渲染进程,触发前端函数。
动态上下文菜单
上下文菜单的一大优势是可以根据用户的操作上下文动态调整。比如在文本编辑器里,有选区时显示“复制”、无选区时将其禁用;在文件列表里,对文件和文件夹显示不同的右键菜单。你可以在构建模板数组时用条件语句轻松实现:
const selectedText = window.getSelection().toString();
const template = [
{ label: '复制', role: 'copy', enabled: !!selectedText },
{ label: '剪切', role: 'cut', enabled: !!selectedText },
{ type: 'separator' },
{ label: '全选', role: 'selectAll' }
];
window.electronAPI.showContextMenu(template);
这样就把菜单与前端状态紧密联系了起来,体验非常自然。
实用技巧
- 快捷键提示:在菜单项的
accelerator文字中,Electron 会自动在菜单右侧显示快捷键组合,为高级用户提供操作效率。 - 禁用与隐藏:通过
enabled: false让菜单项变灰,visible: false则彻底隐藏,可根据应用逻辑实时更新(注意:一旦菜单赋值给Menu.setApplicationMenu()后,要动态更新菜单项状态需要重新构建菜单并设置,或者保持菜单引用并调用menuItem.enabled = value)。 - 菜单中的图标:Electron 的
Menu不支持直接在每个菜单项中嵌入图标(macOS 系统限制),但你可以通过在模板的label中使用特殊字符或 Unicode 符号来模拟简单的视觉标识。 - 上下文菜单与 DevTools:开发阶段如果默认的右键菜单被 DevTools 接管,可以在
template中加入{ label: '检查元素', accelerator: 'CmdOrCtrl+Shift+I', click: () => { win.webContents.toggleDevTools(); } }这样的自定义项来保留调试入口。
通过以上方式,你的应用就可以拥有完全原生体验的菜单交互,无论是顶部的功能栏还是右键唤起的快捷操作,都能让用户上手即用,零学习成本。