Electron 菜单、托盘与多窗口
🎯 引言
这篇文章介绍两个常见功能:点击菜单里的“设置”打开设置窗口,以及点击托盘图标找回已经隐藏的主窗口。
这个功能由主进程完成,因为应用菜单和窗口都属于 Electron 的桌面能力,不属于网页里的 DOM。
学完本篇,你将能够:
- 用
Menu创建应用菜单。 - 给菜单项添加
click点击事件。 - 在
click中创建新的BrowserWindow。 - 理解托盘图标的位置和用途。
🧱 准备一个设置页面
先创建 settings.html,它就是设置窗口要显示的页面。
请把 settings.html 放在 main.js 同一级目录:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'" />
<title>设置</title>
</head>
<body>
<h1>设置窗口</h1>
<p>如果你能看到这个页面,说明菜单已经成功打开了新窗口。</p>
</body>
</html>
这一步只是准备页面,还不会看到新窗口。
🛠 编写主进程代码
接下来替换 main.js。
这段代码运行后的效果是:点击菜单栏里的“文件 → 设置”,打开刚才创建的 settings.html。
const { app, BrowserWindow, Menu } = require('electron/main');
const path = require('node:path');
function createMainWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
});
mainWindow.loadFile(path.join(__dirname, 'index.html'));
}
function createMenu() {
const template = [];
if (process.platform === 'darwin') {
template.push({ role: 'appMenu' });
}
template.push({
label: '文件',
submenu: [
{
label: '设置',
click: () => {
const settingsWindow = new BrowserWindow({
width: 520,
height: 420,
});
settingsWindow.loadFile(path.join(__dirname, 'settings.html'));
},
},
{ type: 'separator' },
{ role: 'quit' },
],
});
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
app.whenReady().then(() => {
createMainWindow();
createMenu();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
运行项目:
npm start
现在点击“文件 → 设置”,应该会打开一个新的设置窗口。
如果你使用 macOS,请先点击 Electron 窗口,让应用获得焦点,再去屏幕最上方找“文件”菜单。
✨ 菜单代码怎么理解
createMenu 负责创建应用菜单。
先看这几行:
const template = [];
if (process.platform === 'darwin') {
template.push({ role: 'appMenu' });
}
template 是菜单配置数组。
macOS 有一个特殊规则:第一个菜单通常会显示成应用名称。所以在 macOS 上,我们先放入 { role: 'appMenu' },再添加自己的“文件”菜单。
再看真正的“文件”菜单:
template.push({
label: '文件',
submenu: [
{
label: '设置',
click: () => {
const settingsWindow = new BrowserWindow({
width: 520,
height: 420,
});
settingsWindow.loadFile(path.join(__dirname, 'settings.html'));
},
},
{ type: 'separator' },
{ role: 'quit' },
],
});
这里的关系很简单:
label: '文件':菜单栏上的“文件”。label: '设置':“文件”下面的“设置”菜单项。click:点击“设置”时执行的函数。new BrowserWindow():创建新窗口。loadFile():让新窗口显示settings.html。
也就是说,真正打开设置窗口的是这两行:
const settingsWindow = new BrowserWindow({
width: 520,
height: 420,
});
settingsWindow.loadFile(path.join(__dirname, 'settings.html'));
🪤 常见问题
看不到“文件”菜单
如果你是 macOS,菜单在屏幕最上方,不在窗口里面。先点击 Electron 窗口,让应用获得焦点,再查看屏幕顶部菜单栏。
点击“设置”没有反应
先检查 settings.html 是否和 main.js 在同一个目录。还要完全退出 Electron,再重新运行 npm start。
每点一次都会打开一个新设置窗口
这是正常现象。本文先讲清楚“菜单点击打开窗口”。如果要限制只打开一个设置窗口,需要额外保存窗口引用,后面再学。
为什么菜单代码要写在主进程里
应用菜单是桌面应用的一部分,不是 HTML 页面里的元素,所以它由主进程创建。
🖥 托盘是什么
Electron 的 Tray 指的是应用放在系统桌面边缘的一个小图标。它不是 macOS 的程序坞,也不是 Windows 任务栏的整体:
| 系统 | 托盘通常出现的位置 | 它和什么不同 |
|---|---|---|
| Windows | 任务栏右侧的通知区域,也叫系统托盘 | 任务栏还负责显示打开的窗口,托盘只是其中一小块区域 |
| macOS | 屏幕顶部菜单栏右侧的状态区域 | 程序坞负责启动和切换应用,托盘图标负责显示应用菜单或状态 |
| Linux | 取决于桌面环境,通常是面板上的状态区域 | 不同桌面环境对托盘支持可能不同 |
托盘适合放置需要在后台运行的应用,例如下载工具、同步工具或聊天工具。用户点击图标后,可以显示主窗口、暂停任务或退出应用。
托盘通常由主进程创建,并保存主窗口引用。下面的例子创建一个托盘菜单,菜单中的“显示窗口”会把已经隐藏的主窗口显示出来:
const { Tray, Menu } = require('electron/main');
let tray;
function createTray() {
tray = new Tray(path.join(__dirname, 'assets/tray.png'));
tray.setContextMenu(
Menu.buildFromTemplate([
{
label: '显示窗口',
click: () => mainWindow.show(),
},
{
label: '退出',
role: 'quit',
},
]),
);
}
窗口、托盘和菜单都由主进程管理。窗口关闭后要同步清理引用,创建多个窗口时也要明确哪个窗口负责哪个功能,避免每次点击菜单都无条件创建新窗口。
🧾 小节总结
- Electron 应用菜单通过
Menu创建。 Menu.buildFromTemplate把菜单配置转换成菜单对象。Menu.setApplicationMenu把菜单安装到应用中。- 菜单项的
click会在用户点击后执行。 - 在
click中创建BrowserWindow,就可以通过菜单打开新窗口。 Tray是桌面边缘的应用状态图标,不等于程序坞或整个任务栏。- macOS 的菜单显示在屏幕最上方,不在窗口内部。
❓ 知识问答
Q1:菜单可以写在 index.html 里吗?
A:不可以。本文讲的是桌面应用菜单,不是网页按钮,所以要在主进程中创建。
Q2:click 会自动执行吗?
A:不会。只有用户点击对应菜单项时,click 里的函数才会执行。
Q3:设置窗口为什么要加载 settings.html?
A:BrowserWindow 只负责创建窗口,窗口里面显示什么内容,需要通过 loadFile 指定。
🧪 小练习
练习一:把设置窗口尺寸改成 600 × 400。
const settingsWindow = new BrowserWindow({
// 请在这里编写代码
});
练习二:把“设置”菜单项改名为“偏好设置”。
{
label: '偏好设置',
click: () => {
// 保持原来的窗口创建代码
},
}
🎉 恭喜你已经掌握 Electron 通过菜单打开窗口的基本写法啦!
