Skip to content

Electron - 菜单

桌面应用程序通常有两种菜单:主应用菜单(通常位于屏幕或窗口顶部)和上下文菜单(右键单击时出现)。Electron 提供了创建这两种菜单的模块。

你主要会使用 Electron 的 Menu 和 MenuItem 模块。这些模块在主进程中可用。要创建从渲染进程触发的动态上下文菜单,你需要使用进程间通信 (IPC) 来请求主进程创建并显示菜单。这比使用已废弃的 remote 模块更安全。

应用菜单在主进程中定义。以下是在你的 main.js 文件中设置典型应用菜单的示例:

const { app, BrowserWindow, Menu, ipcMain } = require('electron');
const path = require('path');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'), // 对 IPC 很重要
contextIsolation: true, // 推荐用于安全性
nodeIntegration: false // 推荐用于安全性
}
});
mainWindow.loadFile(path.join(__dirname, 'index.html'));
// mainWindow.webContents.openDevTools(); // 可选:打开 DevTools
}
// 应用菜单模板
const menuTemplate = [
{
label: 'File',
submenu: [
{ label: 'New Window', click: () => { /* 实现新建窗口的逻辑 */ } },
{ type: 'separator' },
app.isPackaged ? { role: 'close' } : { role: 'quit' } // 根据需要显示“关闭”或“退出”
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'selectAll' }
]
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
{
role: 'window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' }, // 在 macOS 上,这是绿点按钮的行为
...(process.platform === 'darwin' ? [
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ role: 'window' } // 在 macOS 上的“窗口”菜单
] : [
{ role: 'close' }
])
]
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click: async () => {
const { shell } = require('electron');
await shell.openExternal('https://electronjs.org');
}
}
]
}
];
// Adapt menu for macOS
if (process.platform === 'darwin') {
menuTemplate.unshift({
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
});
}
app.on('ready', () => {
createWindow();
const mainMenu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(mainMenu);
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// 上下文菜单的 IPC 处理器(参阅下一节)
ipcMain.on('show-context-menu', (event) => {
const contextMenuTemplate = [
{
label: 'Inspect Element',
click: () => {
BrowserWindow.fromWebContents(event.sender)?.webContents.inspectElement(0, 0);
}
},
{ type: 'separator' },
{ label: 'Custom Action 1', click: () => console.log('Custom Action 1 executed') },
];
const menu = Menu.buildFromTemplate(contextMenuTemplate);
menu.popup({ window: BrowserWindow.fromWebContents(event.sender) });
});
// 确保你的项目目录中有 index.html 和 preload.js 文件。

此代码设置了一个标准的应用菜单。我们定义了一个 menuTemplate(一个对象数组),然后使用 Menu.buildFromTemplate() 和 Menu.setApplicationMenu() 构建并设置它。role 属性允许 Electron 为常见的菜单项使用预定义的行为和标签,并使其适应操作系统。

请注意 BrowserWindow 中的 webPreferences。contextIsolation 和 nodeIntegration: false 是关键的安全实践。preload.js 脚本对于通过 IPC 安全地向渲染进程暴露特定的 Node.js/Electron 功能至关重要。

通过 IPC 创建上下文菜单(右键菜单)

Section titled “通过 IPC 创建上下文菜单(右键菜单)”

上下文菜单通常从渲染进程(你的 HTML/JavaScript 文件)触发。由于出于安全原因 remote 模块已被废弃,我们使用 IPC 请求主进程显示上下文菜单。

以下是设置方法:

此脚本在特权环境中运行,可以访问渲染器的 window 对象和 Node.js 模块。它充当桥梁。

// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
showContextMenu: () => ipcRenderer.send('show-context-menu')
// 如果你的上下文菜单需要来自渲染器的数据,可以在这里传递参数
// 例如:showContextMenu: (someData) => ipcRenderer.send('show-context-menu', someData)
});

此代码将一个名为 window.electronAPI.showContextMenu 的函数暴露给你的渲染进程。调用此函数时,会向主进程发送一个名为 ‘show-context-menu’ 的 IPC 消息。

在你的 HTML 文件中,添加一个脚本来监听 contextmenu 事件并调用暴露的 API。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron Menus Demo</title>
<style>
body { font-family: sans-serif; height: 100vh; display: flex; align-items: center; justify-content: center; margin: 0; }
.container { padding: 20px; border: 1px solid #ccc; border-radius: 5px; background-color: #f9f9f9; }
</style>
</head>
<body>
<div class="container">
<p>Right-click anywhere in this window to see a context menu.</p>
</div>
<script>
// 此脚本在渲染进程中运行
window.addEventListener('contextmenu', (event) => {
event.preventDefault(); // 阻止默认的浏览器上下文菜单
window.electronAPI.showContextMenu(); // 调用由 preload.js 暴露的 API
}, false);
</script>
</body>
</html>

主进程需要监听 ‘show-context-menu’ 消息。我们已经在应用菜单部分的 main.js 示例中添加了此处理器。为了清晰起见,在此再次列出:

// 如果你的 main.js 中还没有,请添加此段
// const { ipcMain, Menu, BrowserWindow } = require('electron'); // 确保这些已导入
ipcMain.on('show-context-menu', (event) => {
const contextMenuTemplate = [
{
label: 'Inspect Element',
click: () => {
// event.sender 是发送消息的 WebContents
BrowserWindow.fromWebContents(event.sender)?.webContents.inspectElement(0, 0);
}
},
{ type: 'separator' },
{ label: 'Custom Action 1', click: () => console.log('Custom Action 1 executed from context menu') },
{ label: 'Another Action', type: 'checkbox', checked: true }
];
const menu = Menu.buildFromTemplate(contextMenuTemplate);
// menu.popup() 用于显示菜单。
// 我们传入与发送事件的渲染进程关联的窗口。
menu.popup({ window: BrowserWindow.fromWebContents(event.sender) });
});

当你在应用窗口中右键单击时,index.html 中的 contextmenu 事件会被捕获,window.electronAPI.showContextMenu() 函数被调用,从而发送一个 IPC 消息。main.js 中的 ipcMain 监听器接收到此消息,从 contextMenuTemplate 构建一个菜单,并在光标位置显示它。

这种基于 IPC 的方法更安全,并且符合现代 Electron 开发实践。它清晰地分离了主进程和渲染进程之间的职责。