測驗:Electron + React 教學 #04 系統整合
共 5 題,點選答案後會立即顯示結果
1. 在 Electron 中建立應用程式選單時,Menu.buildFromTemplate(template) 的 template 參數是什麼結構?
2. 在建立系統托盤(Tray)時,為什麼通常將 tray 變數宣告在全域作用域?
3. 選單項目中的 role 屬性有什麼作用?
{ label: ‘離開’, role: ‘quit’ }
4. 從渲染程序發送系統通知,文章推薦使用哪種方式?
5. 在 macOS 上建立應用程式選單時,有什麼特別需要注意的地方?
前言
當你用 AI 輔助閱讀 Electron 專案時,經常會看到 Menu、Tray、Notification 這些 API。這些是讓桌面應用程式「像個真正的桌面應用程式」的關鍵功能——應用程式選單、系統托盤圖示、原生通知。本篇將教你如何讀懂這些系統整合的程式碼。
學習目標
讀完本篇後,你將能夠:
- 讀懂自訂應用程式選單(Menu)的程式碼
- 理解系統托盤(Tray)的實作方式
- 看懂系統原生通知(Notification)的使用方法
Menu API:建立應用程式選單
最小範例
// main.js - 主程序
const { app, Menu } = require('electron')
const template = [
{
label: '檔案',
submenu: [
{ label: '新增', accelerator: 'CmdOrCtrl+N' },
{ label: '開啟', accelerator: 'CmdOrCtrl+O' },
{ type: 'separator' },
{ label: '離開', role: 'quit' }
]
}
]
app.whenReady().then(() => {
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
})
Code language: JavaScript (javascript)讀懂這段程式碼
當你在專案中看到類似程式碼時,注意這幾個重點:
1. template 陣列結構
const template = [
{
label: '選單名稱', // 顯示在選單列的文字
submenu: [ ... ] // 下拉選單項目
}
]
Code language: JavaScript (javascript)這是選單的「藍圖」,每個物件代表一個頂層選單。
2. 選單項目屬性
{
label: '新增', // 顯示文字
accelerator: 'CmdOrCtrl+N', // 快捷鍵
click: () => { /* 處理函式 */ }, // 點擊事件
type: 'separator', // 分隔線
role: 'quit' // 內建角色
}
Code language: JavaScript (javascript)3. role 內建角色
看到 role 時,表示使用 Electron 內建功能:
{ role: 'quit' } // 離開應用程式
{ role: 'copy' } // 複製
{ role: 'paste' } // 貼上
{ role: 'undo' } // 復原
{ role: 'toggleDevTools' } // 開發者工具
Code language: JavaScript (javascript)右鍵選單(Context Menu)
// main.js
const { Menu } = require('electron')
const contextMenu = Menu.buildFromTemplate([
{ label: '複製', role: 'copy' },
{ label: '貼上', role: 'paste' },
{ type: 'separator' },
{ label: '全選', role: 'selectAll' }
])
// 在視窗中使用
mainWindow.webContents.on('context-menu', () => {
contextMenu.popup()
})
Code language: PHP (php)讀到這段時,關鍵是 popup() 方法——它會在滑鼠位置顯示選單。
Tray API:系統托盤圖示
系統托盤就是 Windows 右下角或 macOS 右上角的小圖示區域。
最小範例
// main.js
const { app, Tray, Menu } = require('electron')
const path = require('path')
let tray = null // 必須保存參考,避免被垃圾回收
app.whenReady().then(() => {
tray = new Tray(path.join(__dirname, 'icon.png'))
const contextMenu = Menu.buildFromTemplate([
{ label: '顯示視窗', click: () => mainWindow.show() },
{ label: '離開', role: 'quit' }
])
tray.setToolTip('我的應用程式')
tray.setContextMenu(contextMenu)
})
Code language: JavaScript (javascript)讀懂這段程式碼
1. 為什麼要用全域變數?
let tray = null // 寫在最外層
Code language: JavaScript (javascript)這是常見模式。如果 tray 是區域變數,會被 JavaScript 垃圾回收機制回收,托盤圖示就會消失。
2. 圖示路徑
new Tray(path.join(__dirname, 'icon.png'))
Code language: JavaScript (javascript)- 圖示大小建議:16×16 或 22×22 像素
- macOS 使用「Template Image」效果更好(檔名加
Template,如iconTemplate.png)
3. 托盤事件
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
})
tray.on('double-click', () => {
mainWindow.show()
})
Code language: PHP (php)實用模式:最小化到托盤
// 視窗關閉時隱藏而非退出
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault()
mainWindow.hide()
}
})
// 真正要退出時設定標記
app.on('before-quit', () => {
app.isQuitting = true
})
Code language: PHP (php)讀到這段程式碼時,理解流程:
- 點擊視窗的 X 按鈕 → 觸發
close事件 event.preventDefault()阻止預設關閉行為mainWindow.hide()只是隱藏視窗- 從托盤選單選「離開」→ 設定
app.isQuitting = true - 再次觸發關閉時,不會被阻止
Notification API:系統原生通知
最小範例
// main.js - 主程序
const { Notification } = require('electron')
function showNotification() {
const notification = new Notification({
title: '下載完成',
body: '檔案已儲存到下載資料夾'
})
notification.show()
}
Code language: JavaScript (javascript)讀懂這段程式碼
1. 基本選項
new Notification({
title: '標題', // 通知標題
body: '內文', // 通知內容
icon: '/path/icon.png', // 圖示(可選)
silent: false, // 是否靜音
urgency: 'normal' // 緊急程度(Linux)
})
Code language: JavaScript (javascript)2. 通知事件
notification.on('click', () => {
// 使用者點擊通知
mainWindow.show()
})
notification.on('close', () => {
// 通知被關閉
})
Code language: PHP (php)從渲染程序發送通知
有兩種方式:
方式一:使用 Web API(推薦)
// renderer.js - 渲染程序
new window.Notification('標題', {
body: '內文'
})
Code language: JavaScript (javascript)這是標準 Web API,不需要 IPC。
方式二:透過 IPC
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
showNotification: (title, body) =>
ipcRenderer.invoke('show-notification', title, body)
})
// main.js
ipcMain.handle('show-notification', (event, title, body) => {
new Notification({ title, body }).show()
})
Code language: JavaScript (javascript)跨平台差異處理
選單的平台差異
macOS 的選單結構與 Windows/Linux 不同:
const isMac = process.platform === 'darwin'
const template = [
// macOS 第一個選單是應用程式名稱
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []),
{
label: '檔案',
submenu: [
isMac ? { role: 'close' } : { role: 'quit' }
]
}
]
Code language: JavaScript (javascript)讀懂這段的關鍵:
process.platform === 'darwin'判斷是否為 macOS- 使用展開運算子
...條件性加入選單項目
托盤的平台差異
// macOS 需要使用 Template Image
const iconPath = process.platform === 'darwin'
? path.join(__dirname, 'iconTemplate.png')
: path.join(__dirname, 'icon.png')
tray = new Tray(iconPath)
// Windows 支援氣球通知
if (process.platform === 'win32') {
tray.displayBalloon({
title: '應用程式已啟動',
content: '點擊托盤圖示開啟主視窗'
})
}
Code language: JavaScript (javascript)通知的平台差異
// 檢查通知是否支援
if (Notification.isSupported()) {
new Notification({ title, body }).show()
}
// Windows 需要設定 AppUserModelId(打包時設定)
if (process.platform === 'win32') {
app.setAppUserModelId('com.mycompany.myapp')
}
Code language: JavaScript (javascript)實作範例:帶托盤的常駐應用程式
這是一個整合所有功能的完整範例:
// main.js
const { app, BrowserWindow, Menu, Tray, Notification } = require('electron')
const path = require('path')
let mainWindow = null
let tray = null
// 建立應用程式選單
function createMenu() {
const template = [
{
label: '檔案',
submenu: [
{
label: '新增通知',
accelerator: 'CmdOrCtrl+N',
click: () => showNotification('測試', '這是一則測試通知')
},
{ type: 'separator' },
{ label: '離開', role: 'quit' }
]
},
{
label: '視窗',
submenu: [
{ label: '最小化到托盤', click: () => mainWindow.hide() },
{ label: '重新載入', role: 'reload' }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
// 建立系統托盤
function createTray() {
const iconPath = path.join(__dirname, 'icon.png')
tray = new Tray(iconPath)
const contextMenu = Menu.buildFromTemplate([
{ label: '顯示視窗', click: () => mainWindow.show() },
{ label: '隱藏視窗', click: () => mainWindow.hide() },
{ type: 'separator' },
{ label: '發送通知', click: () => showNotification('來自托盤', '點擊了托盤選單') },
{ type: 'separator' },
{ label: '離開', click: () => {
app.isQuitting = true
app.quit()
}}
])
tray.setToolTip('我的常駐應用程式')
tray.setContextMenu(contextMenu)
// 點擊托盤圖示切換視窗顯示
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
})
}
// 發送通知
function showNotification(title, body) {
if (Notification.isSupported()) {
const notification = new Notification({ title, body })
notification.on('click', () => mainWindow.show())
notification.show()
}
}
// 建立主視窗
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
// 關閉視窗時隱藏到托盤
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault()
mainWindow.hide()
// 首次隱藏時提示使用者
showNotification('應用程式仍在執行', '點擊托盤圖示可重新開啟')
}
})
}
app.whenReady().then(() => {
createWindow()
createMenu()
createTray()
})
app.on('before-quit', () => {
app.isQuitting = true
})
Code language: JavaScript (javascript)閱讀這個範例的重點
- 模組化函式:
createMenu()、createTray()、showNotification()各司其職 - 狀態管理:
app.isQuitting控制是隱藏還是真正關閉 - 使用者體驗:首次最小化時發送通知告知使用者
- 事件串接:托盤點擊、通知點擊都能讓視窗重新出現
常見模式速查
| 需求 | 關鍵程式碼 |
|---|---|
| 建立選單 | Menu.buildFromTemplate(template) |
| 設定應用程式選單 | Menu.setApplicationMenu(menu) |
| 顯示右鍵選單 | menu.popup() |
| 建立托盤 | new Tray(iconPath) |
| 托盤選單 | tray.setContextMenu(menu) |
| 發送通知 | new Notification({ title, body }).show() |
| 判斷平台 | process.platform === 'darwin' |
| 防止關閉 | event.preventDefault() |
除錯技巧
- 托盤圖示不顯示:確認
tray變數是全域的,不會被垃圾回收 - 選單沒反應:檢查是否有
click處理函式 - 通知不出現:用
Notification.isSupported()確認支援度 - macOS 選單怪怪的:第一個選單項目必須是應用程式名稱
小結
本篇介紹了 Electron 三個重要的系統整合 API:
- Menu:建立應用程式選單與右鍵選單,注意
template結構和role內建角色 - Tray:系統托盤圖示,記得保存全域參考避免被回收
- Notification:系統原生通知,可從主程序或渲染程序發送
這些功能讓你的 Electron 應用程式更融入作業系統的使用體驗。下一篇我們將學習如何將應用程式打包發布。
延伸閱讀
進階測驗:Electron + React 教學 #04 系統整合
測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。
共 5 題,包含情境題與錯誤診斷題。
1. 你正在開發一個常駐型應用程式,希望使用者點擊視窗的 X 按鈕時,應用程式只是隱藏到系統托盤而非真正關閉。應該如何實作? 情境題
2. 你的 Electron 應用程式在 macOS 上執行時,選單看起來很奇怪,「檔案」選單的項目跑到了應用程式名稱的位置。以下是你的選單程式碼,問題出在哪裡? 錯誤診斷
const template = [
{
label: ‘檔案’,
submenu: [
{ label: ‘開啟’, accelerator: ‘CmdOrCtrl+O’ },
{ label: ‘離開’, role: ‘quit’ }
]
}
]
3. 你建立了一個系統托盤,但應用程式啟動後托盤圖示立刻消失了。以下是你的程式碼,問題出在哪裡? 錯誤診斷
app.whenReady().then(() => {
const tray = new Tray(path.join(__dirname, ‘icon.png’))
tray.setToolTip(‘我的應用程式’)
createWindow()
})
4. 你需要在應用程式選單中新增一個「複製」功能。以下哪種寫法最合適? 情境題
5. 你的應用程式需要讓使用者真正關閉應用程式(而非只是隱藏到托盤)。在實作「最小化到托盤」功能後,你需要加入一個「真正離開」的機制。應該如何實作? 情境題
// 目前的 close 事件處理
mainWindow.on(‘close’, (event) => {
event.preventDefault()
mainWindow.hide()
})