Electron - 菜单
Electron - 应用菜单和上下文菜单
Section titled “Electron - 应用菜单和上下文菜单”桌面应用程序通常有两种菜单:主应用菜单(通常位于屏幕或窗口顶部)和上下文菜单(右键单击时出现)。Electron 提供了创建这两种菜单的模块。
你主要会使用 Electron 的 Menu 和 MenuItem 模块。这些模块在主进程中可用。要创建从渲染进程触发的动态上下文菜单,你需要使用进程间通信 (IPC) 来请求主进程创建并显示菜单。这比使用已废弃的 remote 模块更安全。
创建应用菜单
Section titled “创建应用菜单”应用菜单在主进程中定义。以下是在你的 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 macOSif (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 请求主进程显示上下文菜单。
以下是设置方法:
1. 预加载脚本 (preload.js)
Section titled “1. 预加载脚本 (preload.js)”此脚本在特权环境中运行,可以访问渲染器的 window 对象和 Node.js 模块。它充当桥梁。
// preload.jsconst { 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 消息。
2. 渲染进程 (index.html)
Section titled “2. 渲染进程 (index.html)”在你的 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>3. 主进程 (main.js - IPC 处理器)
Section titled “3. 主进程 (main.js - IPC 处理器)”主进程需要监听 ‘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 开发实践。它清晰地分离了主进程和渲染进程之间的职责。