系统托盘、应用菜单和全局快捷键是让 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 三者协同的实际场景
一个典型的“剪贴板管理工具”会这样组合使用:
- 系统托盘:程序启动后最小化到托盘,图标悬浮显示当前剪切板条目数。
- 全局快捷键:用户按下
CmdOrCtrl+Shift+V时,弹出窗口并自动聚焦到搜索框。 - 右键菜单:在条目上右键提供“复制”、“删除”、“固定到顶部”等选项。
- 应用菜单:提供“历史记录”、“设置”、“帮助”等下拉菜单,其中“设置”可以打开快捷键自定义页面。
这种设计让用户无需频繁打开窗口,又不失完整的桌面操作体验,是高级 Electron 应用的必备模式。
通过本章学习,你应该能够为主进程添加一个带菜单的系统托盘图标,并利用全局快捷键让应用呼之即来。下一节我们将深入应用打包与更新的实战,让这些功能最终到达用户桌面。