Electron 原生功能集成
Electron 之所以强大,是因为它不仅能写前端,还能访问系统的原生功能,比如菜单、对话框、托盘、通知和全局快捷键等。
本章节我们来看看如何通过主进程模块调用这些功能。
菜单系统
Electron 提供 Menu
模块用于创建菜单栏、上下文菜单和系统托盘菜单。
应用菜单(Menu)
应用菜单是显示在窗口顶部(macOS)或应用窗口中的菜单栏。
实例
const { app, Menu } = require('electron')
const template = [
{
label: '文件',
submenu: [
{ label: '新建文件', accelerator: 'CmdOrCtrl+N', click: () => console.log('新建文件') },
{ label: '打开...', accelerator: 'CmdOrCtrl+O' },
{ type: 'separator' },
{ label: '退出', role: 'quit' }
]
},
{
label: '编辑',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
const template = [
{
label: '文件',
submenu: [
{ label: '新建文件', accelerator: 'CmdOrCtrl+N', click: () => console.log('新建文件') },
{ label: '打开...', accelerator: 'CmdOrCtrl+O' },
{ type: 'separator' },
{ label: '退出', role: 'quit' }
]
},
{
label: '编辑',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
说明:
label
:菜单标题。submenu
:子菜单数组。accelerator
:快捷键(macOS、Windows 通用写法CmdOrCtrl+X
)。role
:系统预定义行为,如quit
、copy
、paste
。click
:点击事件回调函数。
上下文菜单(Context Menu)
右键菜单通常在渲染进程中触发:
实例
// main.js
const { ipcMain, Menu } = require('electron')
ipcMain.on('show-context-menu', (event) => {
const menu = Menu.buildFromTemplate([
{ label: '刷新', click: () => event.sender.reload() },
{ label: '复制', role: 'copy' },
{ label: '粘贴', role: 'paste' }
])
menu.popup()
})
const { ipcMain, Menu } = require('electron')
ipcMain.on('show-context-menu', (event) => {
const menu = Menu.buildFromTemplate([
{ label: '刷新', click: () => event.sender.reload() },
{ label: '复制', role: 'copy' },
{ label: '粘贴', role: 'paste' }
])
menu.popup()
})
实例
// renderer.js
window.addEventListener('contextmenu', (e) => {
e.preventDefault()
window.electronAPI.showContextMenu()
})
window.addEventListener('contextmenu', (e) => {
e.preventDefault()
window.electronAPI.showContextMenu()
})
系统托盘菜单
在系统通知区域显示图标与菜单。
实例
const { Tray, Menu, nativeImage } = require('electron')
let tray = null
function createTray() {
const icon = nativeImage.createFromPath('icon.png')
tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([
{ label: '打开主窗口', click: () => mainWindow.show() },
{ label: '退出', click: () => app.quit() }
])
tray.setToolTip('我的 Electron 应用')
tray.setContextMenu(contextMenu)
}
let tray = null
function createTray() {
const icon = nativeImage.createFromPath('icon.png')
tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([
{ label: '打开主窗口', click: () => mainWindow.show() },
{ label: '退出', click: () => app.quit() }
])
tray.setToolTip('我的 Electron 应用')
tray.setContextMenu(contextMenu)
}
菜单快捷键
通过 accelerator
定义。例如:
{ label: '保存', accelerator: 'CmdOrCtrl+S', click: saveFile }
CmdOrCtrl
:自动适配 macOS/Windows。Alt+F4
:Windows 关闭窗口。Cmd+Q
:macOS 退出应用。
系统对话框
dialog
模块提供原生文件、消息、错误等系统对话框。
文件/文件夹选择
const { dialog } = require('electron')
async function openFileDialog() {
const result = await dialog.showOpenDialog({
title: '选择文件',
properties: ['openFile', 'multiSelections'],
filters: [{ name: '文本文件', extensions: ['txt', 'md'] }]
})
console.log(result.filePaths)
}
保存对话框
async function saveFileDialog() {
const result = await dialog.showSaveDialog({
title: '保存文件',
defaultPath: '新建文档.txt'
})
console.log(result.filePath)
}
消息/错误对话框
dialog.showMessageBox({
type: 'info',
title: '提示',
message: '操作已完成!'
})
dialog.showErrorBox('错误', '文件保存失败,请重试。')
系统托盘
托盘(Tray)让应用在后台运行时仍能通过系统图标交互。
创建托盘图标
const { Tray } = require('electron') const path = require('path') let tray = new Tray(path.join(__dirname, 'trayIcon.png'))
托盘菜单与提示
const contextMenu = Menu.buildFromTemplate([ { label: '显示窗口', click: () => mainWindow.show() }, { label: '退出', click: () => app.quit() } ]) tray.setToolTip('Electron 应用运行中') tray.setContextMenu(contextMenu)
托盘通知
tray.displayBalloon({ icon: path.join(__dirname, 'icon.png'), title: '新消息', content: '您有一条新通知' })
最小化到托盘
监听关闭事件隐藏窗口:
mainWindow.on('close', (event) => { event.preventDefault() mainWindow.hide() })
通知(Notifications)
Electron 的 Notification
类可跨平台显示系统通知。
系统通知发送
new Notification({ title: '任务完成', body: '您的文件已下载完成' }).show()
通知交互处理
const notif = new Notification({ title: '点击通知', body: '点击我触发事件' }) notif.on('click', () => { console.log('用户点击了通知') }) notif.show()
跨平台兼容性
- macOS、Windows、Linux 均支持;
- macOS 需启用"通知权限";
- Linux 通常需系统支持
libnotify
。
全局快捷键
通过 globalShortcut
注册系统级热键。
注册全局快捷键
const { globalShortcut } = require('electron') app.whenReady().then(() => { globalShortcut.register('CmdOrCtrl+Shift+I', () => { console.log('快捷键触发:打开调试工具') mainWindow.webContents.openDevTools() }) })
快捷键冲突处理
注册前可检测:
if (globalShortcut.isRegistered('CmdOrCtrl+Shift+I')) { console.log('快捷键已被占用') }
平台差异处理
可用条件判断:
const shortcut = process.platform === 'darwin' ? 'Cmd+Shift+T' : 'Ctrl+Shift+T' globalShortcut.register(shortcut, () => console.log('快捷键触发'))
小结
功能 | 模块 | 典型用途 |
---|---|---|
菜单 | Menu |
应用菜单、右键菜单、托盘菜单 |
对话框 | dialog |
打开文件、保存文件、显示提示或错误 |
托盘 | Tray |
后台运行、系统状态展示 |
通知 | Notification |
提示用户事件进度、系统提醒 |
全局快捷键 | globalShortcut |
注册系统级快捷操作 |
Electron 原生功能演示项目
项目结构
my-electron-native-demo/ ├── package.json ├── main.js # 主进程逻辑 ├── preload.js # 预加载脚本(安全通信) └── index.html # 主界面
package.json
注意:不要写注释在 JSON 文件里(否则会报 EJSONPARSE 错误)
{ "name": "my-electron-native-demo", "version": "1.0.0", "description": "Electron 原生功能演示项目", "main": "main.js", "scripts": { "start": "electron ." }, "devDependencies": { "electron": "latest" } }
安装依赖:
npm install
启动应用:
npm start
main.js(主进程)
实例
const { app, BrowserWindow, Menu, Tray, dialog, Notification, globalShortcut, ipcMain } = require('electron')
const path = require('path')
let mainWindow, tray
function createWindow() {
mainWindow = new BrowserWindow({
width: 900,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
// 页面加载完成后显示
mainWindow.once('ready-to-show', () => mainWindow.show())
// 监听窗口关闭:最小化到托盘
mainWindow.on('close', (event) => {
event.preventDefault()
mainWindow.hide()
})
}
function createMenu() {
const template = [
{
label: '文件',
submenu: [
{ label: '打开文件', click: openFile },
{ label: '保存文件', click: saveFile },
{ type: 'separator' },
{ label: '退出', click: () => app.quit() }
]
},
{
label: '工具',
submenu: [
{ label: '显示通知', click: showNotification },
{ label: '关于', click: showAbout }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
function createTray() {
const icon = path.join(__dirname, 'icon.png') // 请准备一张 16x16 或 32x32 图标
tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([
{ label: '显示窗口', click: () => mainWindow.show() },
{ label: '退出', click: () => app.quit() }
])
tray.setToolTip('Electron 原生功能演示')
tray.setContextMenu(contextMenu)
}
function registerShortcuts() {
globalShortcut.register('CmdOrCtrl+Shift+I', () => {
mainWindow.webContents.openDevTools()
})
}
async function openFile() {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: '文本文件', extensions: ['txt', 'md'] }]
})
if (!result.canceled && result.filePaths.length > 0) {
mainWindow.webContents.send('file-opened', result.filePaths[0])
}
}
async function saveFile() {
const result = await dialog.showSaveDialog({
title: '保存文件',
defaultPath: '新建文档.txt'
})
if (!result.canceled) {
dialog.showMessageBox({ type: 'info', message: `文件将保存到:${result.filePath}` })
}
}
function showNotification() {
const notif = new Notification({
title: 'Electron 通知',
body: '这是一条系统通知!'
})
notif.on('click', () => {
dialog.showMessageBox({ message: '你点击了通知!' })
})
notif.show()
}
function showAbout() {
dialog.showMessageBox({
type: 'info',
title: '关于',
message: 'Electron 原生功能演示项目\n版本:1.0.0'
})
}
// 渲染进程调用
ipcMain.handle('show-dialog', async () => {
const result = await dialog.showMessageBox({
type: 'info',
title: '来自主进程',
message: '这是主进程显示的对话框!'
})
return result.response
})
// 生命周期
app.whenReady().then(() => {
createWindow()
createMenu()
createTray()
registerShortcuts()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
const path = require('path')
let mainWindow, tray
function createWindow() {
mainWindow = new BrowserWindow({
width: 900,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
// 页面加载完成后显示
mainWindow.once('ready-to-show', () => mainWindow.show())
// 监听窗口关闭:最小化到托盘
mainWindow.on('close', (event) => {
event.preventDefault()
mainWindow.hide()
})
}
function createMenu() {
const template = [
{
label: '文件',
submenu: [
{ label: '打开文件', click: openFile },
{ label: '保存文件', click: saveFile },
{ type: 'separator' },
{ label: '退出', click: () => app.quit() }
]
},
{
label: '工具',
submenu: [
{ label: '显示通知', click: showNotification },
{ label: '关于', click: showAbout }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
function createTray() {
const icon = path.join(__dirname, 'icon.png') // 请准备一张 16x16 或 32x32 图标
tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([
{ label: '显示窗口', click: () => mainWindow.show() },
{ label: '退出', click: () => app.quit() }
])
tray.setToolTip('Electron 原生功能演示')
tray.setContextMenu(contextMenu)
}
function registerShortcuts() {
globalShortcut.register('CmdOrCtrl+Shift+I', () => {
mainWindow.webContents.openDevTools()
})
}
async function openFile() {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: '文本文件', extensions: ['txt', 'md'] }]
})
if (!result.canceled && result.filePaths.length > 0) {
mainWindow.webContents.send('file-opened', result.filePaths[0])
}
}
async function saveFile() {
const result = await dialog.showSaveDialog({
title: '保存文件',
defaultPath: '新建文档.txt'
})
if (!result.canceled) {
dialog.showMessageBox({ type: 'info', message: `文件将保存到:${result.filePath}` })
}
}
function showNotification() {
const notif = new Notification({
title: 'Electron 通知',
body: '这是一条系统通知!'
})
notif.on('click', () => {
dialog.showMessageBox({ message: '你点击了通知!' })
})
notif.show()
}
function showAbout() {
dialog.showMessageBox({
type: 'info',
title: '关于',
message: 'Electron 原生功能演示项目\n版本:1.0.0'
})
}
// 渲染进程调用
ipcMain.handle('show-dialog', async () => {
const result = await dialog.showMessageBox({
type: 'info',
title: '来自主进程',
message: '这是主进程显示的对话框!'
})
return result.response
})
// 生命周期
app.whenReady().then(() => {
createWindow()
createMenu()
createTray()
registerShortcuts()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
preload.js(安全桥梁)
实例
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
showDialog: () => ipcRenderer.invoke('show-dialog')
})
contextBridge.exposeInMainWorld('electronAPI', {
showDialog: () => ipcRenderer.invoke('show-dialog')
})
index.html(前端界面)
实例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Electron 原生功能演示</title>
<style>
body { font-family: sans-serif; text-align: center; margin-top: 80px; }
button { padding: 10px 20px; margin: 10px; font-size: 16px; cursor: pointer; }
#filePath { color: #666; font-size: 14px; }
</style>
</head>
<body>
<h1>Electron 原生功能演示</h1>
<p><button id="dialogBtn">显示对话框</button></p>
<p id="filePath"></p>
<script>
document.getElementById('dialogBtn').addEventListener('click', async () => {
await window.electronAPI.showDialog()
})
const filePathEl = document.getElementById('filePath')
window.electronAPI && window.electronAPI.fileOpened && window.electronAPI.fileOpened((path) => {
filePathEl.textContent = '打开的文件路径:' + path
})
</script>
</body>
</html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Electron 原生功能演示</title>
<style>
body { font-family: sans-serif; text-align: center; margin-top: 80px; }
button { padding: 10px 20px; margin: 10px; font-size: 16px; cursor: pointer; }
#filePath { color: #666; font-size: 14px; }
</style>
</head>
<body>
<h1>Electron 原生功能演示</h1>
<p><button id="dialogBtn">显示对话框</button></p>
<p id="filePath"></p>
<script>
document.getElementById('dialogBtn').addEventListener('click', async () => {
await window.electronAPI.showDialog()
})
const filePathEl = document.getElementById('filePath')
window.electronAPI && window.electronAPI.fileOpened && window.electronAPI.fileOpened((path) => {
filePathEl.textContent = '打开的文件路径:' + path
})
</script>
</body>
</html>
运行 npm start
后,功能如下:
- 顶部菜单栏包含 "文件"、"工具" 菜单。
- "文件" → 打开文件 / 保存文件 调出系统对话框。
- "工具" → 显示系统通知、显示"关于"窗口。
- 托盘区域出现应用图标,可右键显示菜单。
- 窗口关闭后最小化到托盘。
- 注册全局快捷键
CmdOrCtrl+Shift+I
打开开发者工具。
点我分享笔记