Electron 讓開發者用 Web 技術(HTML、CSS、JavaScript)開發桌面應用程式,系統 Tray(系統匣)是桌面應用的常見功能,讓程式在背景執行時仍可從工具列圖示快速操作。
Electron 基本架構
Electron 分為兩個程序:
- Main Process(主程序):控制應用生命週期、建立視窗、存取系統 API(包含 Tray)
- Renderer Process(渲染程序):負責 UI 顯示,類似瀏覽器環境
Tray 只能在 Main Process 中建立。
建立系統 Tray
基本設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// main.js(Main Process)
const { app, Menu, Tray, nativeImage } = require('electron');
const path = require('path');
let tray = null;
app.whenReady().then(() => {
// 建立 Tray 圖示(建議使用 16x16 或 32x32 的 PNG)
const trayIcon = path.join(__dirname, 'icons', 'icon-16x16.png');
tray = new Tray(trayIcon);
// 設定 Tray 提示文字
tray.setToolTip('我的應用程式');
// 設定右鍵選單
const contextMenu = Menu.buildFromTemplate([
{
label: '顯示視窗',
click: () => {
mainWindow.show();
}
},
{
label: '設定',
click: () => {
// 開啟設定視窗
}
},
{ type: 'separator' },
{
label: '離開',
click: () => {
app.quit();
}
}
]);
tray.setContextMenu(contextMenu);
});
|
點擊事件處理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 點擊 Tray 圖示時顯示/隱藏主視窗
tray.on('click', () => {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
mainWindow.focus();
}
});
// 雙擊事件(Windows 常見行為)
tray.on('double-click', () => {
mainWindow.show();
mainWindow.focus();
});
|
氣泡通知(系統通知)
Electron 使用 Web Notification API 發送系統通知:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Renderer Process 中使用(或透過 ipcMain 從 Main Process 發送)
function showNotification(title, body) {
const notification = new Notification(title, {
body: body,
icon: path.join(__dirname, 'icons', 'icon-128x128.png')
});
notification.onclick = () => {
mainWindow.show();
mainWindow.focus();
};
notification.show();
}
// 呼叫範例
showNotification('新訊息', '你有一則新訊息!');
|
最小化到系統匣(而非關閉)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault();
mainWindow.hide(); // 最小化到 Tray,而非真正關閉
}
});
// 在 Tray 選單的「離開」中
{
label: '離開',
click: () => {
app.isQuiting = true;
app.quit();
}
}
|
動態更新 Tray 圖示
可以透過更換圖示來表示不同狀態(例如有新通知時):
1
2
3
4
5
|
// 切換圖示
function setTrayIcon(hasNotification) {
const iconName = hasNotification ? 'icon-notification.png' : 'icon.png';
tray.setImage(path.join(__dirname, 'icons', iconName));
}
|
electron-builder 打包注意事項
使用 electron-builder 打包時,需要確保 Tray 圖示被正確包含:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// package.json
{
"build": {
"appId": "com.example.myapp",
"win": {
"icon": "build/icon.ico"
},
"mac": {
"icon": "build/icon.icns"
},
"linux": {
"icon": "build/icons"
},
"extraResources": [
{
"from": "src/icons",
"to": "icons",
"filter": ["**/*"]
}
]
}
}
|
圖示格式注意事項:
- Windows:需要
.ico 格式,建議包含多尺寸(16x16 到 256x256)
- macOS:Tray 圖示建議使用模板圖示(純黑白),會自動適應深色/淺色模式
- Linux:使用 PNG 格式
完整 main.js 範例結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
const { app, BrowserWindow, Menu, Tray } = require('electron');
const path = require('path');
let mainWindow;
let tray;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
mainWindow.loadFile('index.html');
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault();
mainWindow.hide();
}
});
}
function createTray() {
const trayIcon = path.join(__dirname, 'icons', 'icon-16x16.png');
tray = new Tray(trayIcon);
tray.setToolTip('My App');
const menu = Menu.buildFromTemplate([
{ label: '顯示', click: () => mainWindow.show() },
{ type: 'separator' },
{ label: '離開', click: () => { app.isQuiting = true; app.quit(); } }
]);
tray.setContextMenu(menu);
tray.on('click', () => mainWindow.show());
}
app.whenReady().then(() => {
createWindow();
createTray();
});
app.on('window-all-closed', (event) => {
event.preventDefault(); // 關閉所有視窗後不退出,繼續在 Tray 運行
});
|
參考資料