Skip to content

Electron - 系统托盘

系统托盘图标(在 Windows 上称为通知区域图标,在 macOS 上称为菜单栏图标)为应用程序提供了一种持续访问和状态指示的方式,即使主应用程序窗口已关闭或隐藏。Electron 允许你创建和管理这些图标及其关联的上下文菜单(Context Menu)。

重要提示:系统托盘图标及其菜单应在主进程(Main Process)中管理,而不是在渲染进程(Renderer Process)中。不鼓励使用(现已废弃且不安全)remote 模块尝试从渲染进程管理它们。

让我们创建一个带有上下文菜单的简单系统托盘图标。你需要一个图标图片(例如,一个 16x16 或 32x32 的 PNG 文件)。对于此示例,假设你在相对于 main.js 文件的 assets 子文件夹中有一个 icon.png 文件。

修改你的 main.js 文件:

const { app, BrowserWindow, Tray, Menu, nativeImage } = require('electron');
const path = require('path');
let mainWindow;
let tray = null; // Keep a reference to the Tray object
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Preload script can be added if renderer needs to communicate about tray actions
// preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
}
});
mainWindow.loadFile(path.join(__dirname, 'index.html'));
mainWindow.on('closed', () => {
mainWindow = null;
});
}
function createTray() {
// It's recommended to use nativeImage.createFromPath for icons.
// Ensure the path is correct and the icon exists.
// For macOS, template images are often preferred (black and white, adapting to theme).
const iconPath = path.join(__dirname, 'assets', 'icon.png'); // Adjust path as needed
const icon = nativeImage.createFromPath(iconPath);
if (icon.isEmpty()) {
console.error('Failed to load tray icon. Is the path correct and image valid?');
// Fallback or error handling
tray = new Tray(nativeImage.createEmpty()); // Creates a blank space, better than crashing
} else {
tray = new Tray(icon);
}
const contextMenu = Menu.buildFromTemplate([
{
label: 'Show App',
click: () => {
if (mainWindow) {
mainWindow.show();
} else {
createWindow();
}
}
},
{
label: 'Item 1',
click: () => {
console.log('Clicked Item 1');
// If you need to notify the renderer, use IPC:
// if (mainWindow) mainWindow.webContents.send('tray-action', 'item1-clicked');
}
},
{
label: 'Quit',
click: () => {
app.quit(); // Quits the application
}
}
]);
tray.setToolTip('My Electron App');
tray.setContextMenu(contextMenu);
// Optional: Handle left-click on tray icon (e.g., toggle window visibility)
tray.on('click', () => {
if (mainWindow) {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
}
}
});
}
app.whenReady().then(() => {
createWindow();
createTray();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// Prevent app from quitting when all windows are closed if tray icon is present (common behavior)
// This depends on your desired UX.
app.on('window-all-closed', () => {
// On macOS, it's common for applications to stay active until the user quits explicitly (Cmd+Q)
// On Windows/Linux, if you want the app to close, remove this or app.quit()
if (process.platform === 'darwin') {
// Do nothing, app stays open with tray icon
} else {
// If you want the app to quit, then app.quit().
// If you want it to stay alive (tray only), then do nothing here.
// For this example, let's assume we want it to stay active if tray is present.
}
});
// Ensure tray is destroyed when app quits to prevent zombie icons on some platforms
app.on('before-quit', () => {
if (tray) {
tray.destroy();
}
});

创建一个简单的 index.html 文件。它的内容对于此托盘示例并不重要,因为所有逻辑都在 main.js 中进行:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron Tray App</title>
</head>
<body>
<h1>My Electron Application</h1>
<p>Check your system tray for the application icon!</p>
</body>
</html>

确保你在 assets 文件夹(例如,your-app-root/assets/icon.png)中有一个 icon.png(或你选择的图标文件)。

使用 npm start 运行应用(假设你的 package.json 已配置好)。

你应该会看到你的应用窗口和系统托盘中的图标。右键单击托盘图标会显示你定义的上下文菜单。点击“显示应用”会聚焦或重新创建窗口,“退出”会退出应用。

  • 图标路径(Icon Paths): 使用 path.join(__dirname, ...) 获取可靠的 assets 路径。nativeImage.createFromPath() 是加载图标的推荐方式。
  • 图标尺寸和类型(Icon Size and Type): 托盘图标很小。通常是 16x16 或 32x32 像素。PNG 是一种常见的格式。macOS 支持“模板图像”(template images),它会根据系统主题调整(通常是黑白)。
  • 主进程逻辑(Main Process Logic): 所有 Tray 和 Menu 的创建和管理必须在主进程中进行。
  • 用于渲染进程交互的 IPC(IPC for Renderer Interaction): 如果一个托盘菜单操作需要影响渲染进程(例如,打开特定视图),使用 IPC(进程间通信,通过 mainWindow.webContents.send() 等)从主进程向渲染进程通信。
  • 生命周期管理(Lifecycle Management): 保留对 Tray 对象的引用(tray = new Tray(...)),以防止它被过早地垃圾回收。在应用的 before-quit 事件中显式销毁托盘图标(tray.destroy())以清理资源,尤其是在 Linux 上。
  • 平台差异(Platform Differences): 托盘图标的行为和外观在 macOS、Windows 和 Linux 上可能略有差异。在所有目标平台上进行测试。

对于更高级的托盘功能,例如动态更新图标或处理不同的点击事件,请参考 Electron Tray 文档。