创建项目

1
pnpm create @quick-start/electron my-app --template vue

electron-vite: https://cn.electron-vite.org/guide/

进程

进程分为:主进程渲染进程Electron 中,每个应用有一个主进程和一个或多个渲染进程

主进程:

  • 负责运行 package.json 的 main 脚本,并执行应用的整个生命周期
  • 控制所有的 Web 页面和与它们的交互
  • 只有在主进程中才能调用某些 Electron 的原生 API
  • 一个 Electron 应用始终有一个主进程

渲染进程:

  • 每个 Electron 的 BrowserWindow 在其自己的渲染进程中运行 Web 页面
  • 负责页面的渲染,即页面上的用户界面
  • 一个 Electron 应用可以有多个渲染进程
  • 与主进程相对独立,但二者之间可以通过 IPC(进程间通讯)进行通信

进程通信

在 Electron 中,渲染进程(即网页)可以通过多种方式与主进程(即 Node.js 环境)进行通信。以下是几种常见的方法:

主进程到渲染进程

webContents.send

描述:主进程向特定的渲染进程发送消息

主进程

1
2
3
import { BrowserWindow } from 'electron'
let win = new BrowserWindow() // 创建窗口
win.webContents.send('main-to-renderer', { key: 'value' })

渲染进程

1
2
3
electron.ipcRenderer.on('main-to-renderer', (event, data) => {
console.log(data) // 输出: { key: 'value' }
})

渲染进程到主进程

ipcRenderer.send

描述:渲染进程向主进程发送消息,不等待响应

渲染进程

1
2
3
4
5
6
electron.ipcRenderer.send('renderer-to-main', { key: 'value' })

// 接收主进程的回复
electron.ipcRenderer.on('reply-channel', (event, arg) => {
console.log(arg)
})

主进程

1
2
3
4
5
6
7
8
import { ipcMain } from 'electron'
ipcMain.on('renderer-to-main', (event, data) => {
console.log(data) // 输出: { key: 'value' }
// 回复渲染进程
// event.reply 是主进程在接收到渲染进程发送的消息后,可以向该渲染进程回复消息
// 适用于渲染进程向主进程发送消息后,主进程需要回复消息的场景(适合需要持续通信)
event.reply('reply-channel', 'reply message')
})

打开外部网页

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts">
const handleClick = () => {
electron.ipcRenderer.send('open', 'open')
}
</script>

<template>
<div class="dashboard-page">
<el-button type="primary" @click="handleClick">open</el-button>
</div>
</template>

<style lang="scss" scoped></style>
1
2
3
4
ipcMain.on('open', (event, arg) => {
// mainWindow.loadURL('http://localhost:4000')
mainWindow.loadURL('https://www.baidu.com')
})

ipcRenderer.invoke

描述:渲染进程向主进程发送消息,并等待响应,返回一个Promise对象,用于需要同步或异步获取主进程处理结果的情况

渲染进程

1
2
3
4
5
6
7
8
// 向主进程发送消息并等待响应
electron.ipcRenderer.invoke('renderer-to-main-invoke', { key: 'value' })
.then(response => {
console.log(response) // 输出主进程返回的响应
})
.catch(err => {
console.error(err)
})

主进程

1
2
3
4
5
import { ipcMain } from 'electron'
ipcMain.handle('renderer-to-main-invoke', async (event, data) => {
console.log(data) // 输出: { key: 'value' }
return { response: '主进程的响应' }
})

渲染进程到渲染进程

渲染进程Electron本身不直接支持渲染进程之间的直接通信,但可以通过主进程作为中介来实现
描述:一个渲染进程发送消息到主进程,主进程再将消息转发到另一个渲染进程

渲染进程A

1
electron.ipcRenderer.send('rendererA-to-main', { key: 'value' })

主进程

1
2
3
4
5
import { ipcMain, BrowserWindow } from 'electron'
let winB = new BrowserWindow() // 创建窗口B 或者 使用已有窗口
ipcMain.on('rendererA-to-main', (event, data) => {
winB.webContents.send('main-to-rendererB', data)
})

渲染进程B

1
2
3
4
5
import { ipcMain, BrowserWindow } from 'electron'
let winB = new BrowserWindow()
ipcMain.on('rendererA-to-main', (event, data) => {
winB.webContents.send('main-to-rendererB', data)
})

总结

  • 主进程到渲染进程:使用webContents.send。
  • 渲染进程到主进程:使用ipcRenderer.send(不等待响应)或ipcRenderer.invoke(等待响应)。
  • 渲染进程到渲染进程:通过主进程中转,使用ipcRenderer.send和webContents.send。

退出应用

app.quit

用户操作退出

1
2
3
4
5
6
7
8
9
10
11
// 在渲染进程中 用户点击按钮退出
const close = () => {
electron.ipcRenderer.send('close')
}

import { app, ipcMain } from 'electron'

// 主进程监听事件
ipcMain.on('close', () => {
app.quit()
})

自定义菜单退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 渲染进程
import { app, Menu } from 'electron'
const template = [
{
label: 'File',
submenu: [
{
label: 'Exit',
click() {
app.quit()
}
}
]
}
]

const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

app.exit

app.exit 方法,表示强制退出应用

1
2
3
4
5
6
import { app } from 'electron'

// 在需要退出应用的地方调用
app.exit(0) // 正常退出应用
app.exit(1) // 表示应用由于错误而退出

关闭主窗口退出应用

1
2
3
4
5
mainWindow.on('close', () => {
... // 窗口关闭时执行的操作
// 在窗口关闭时退出应用
mainWindow.close()
})

窗口操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建主窗口
const mainWindow = new BrowserWindow({
// 窗口大小
width: 900,
// 窗口高度
height: 670,
// 窗口是否可调整大小
show: false,
// false 关闭无边框窗口隐藏任务栏(放大、缩小、关闭)
frame: false,
// 窗口是否置顶
autoHideMenuBar: false,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})

关闭窗口

如果你只想关闭一个特定的窗口,可以使用 BrowserWindow 提供的 close

1
2
3
4
5
6
7
8
import { BrowserWindow } from 'electron'

// 创建窗口
let mainWindow = new BrowserWindow({ width: 800, height: 600 })

// 窗口名.close()
// 关闭窗口
mainWindow.close()

监听窗口关闭事件

你可以在窗口关闭时执行某项操作,比如保存窗口状态、发送通知等

1
2
3
4
5
6
7
8
import { app, BrowserWindow } from 'electron'

let mainWindow = new BrowserWindow({ width: 800, height: 600 })

mainWindow.on('close', () => {
... // 窗口关闭时执行的操作
app.quit()
})

最小化窗口

1
2
3
4
5
6
7
8
9
// 渲染进程
electron.ipcRenderer.send('minimize')

// 主进程
import { ipcMain } from 'electron'

ipcMain.on('minimize', (event) => {
mainWindow.minimize()
})

封装

汇总

  • 最小化窗口:mainWindow.minimize()
  • 最大化窗口:mainWindow.maximize()
  • 还原窗口:mainWindow.unmaximize()
  • 全屏窗口:mainWindow.setFullScreen(true)
  • 退出全屏:mainWindow.setFullScreen(false)
  • 关闭窗口:mainWindow.close()
  • 隐藏窗口:mainWindow.hide()
  • 显示窗口:mainWindow.show()
  • 窗口置顶:mainWindow.setAlwaysOnTop(true)
  • 取消窗口置顶:mainWindow.setAlwaysOnTop(false)
  • 窗口透明度:mainWindow.setOpacity(0.5)
  • 窗口尺寸:mainWindow.setSize(800, 600)
  • 窗口位置:mainWindow.setPosition(100, 100)
  • 窗口标题:mainWindow.setTitle('标题')
  • 窗口背景色:mainWindow.setBackgroundColor('#333')

菜单

系统托盘

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
50
51
52
53
54
55
56
57
58
59
60
//主进程
import { app, Tray, Menu } from 'electron'

// 是否退出应用,初始化为false,退出时设置为true
app.isQuiting = false

// 阻止应用退出 如果主窗口关闭应用会直接退出
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
// 阻止窗口关闭默认事件
event.preventDefault()
mainWindow.hide()
}
})
// 创建托盘图标
const iconPath = path.join(__dirname, '../../resources/icon.png?asset')
// 实例化托盘图标
const trayIcon = nativeImage.createFromPath(icon)
let tray = new Tray(trayIcon)

// 创建菜单模板
const contextMenu = Menu.buildFromTemplate([
{
label: '显示应用',
click: () => {
// 显示主窗口
mainWindow.show()
}
},
// 菜单分割线
{ type: 'separator' },
{
label: '退出',
click: () => {
app.quit()
}
}
])

// 设置托盘菜单
tray.setContextMenu(contextMenu)
// 设置托盘图标提示信息,鼠标悬停显示
tray.setToolTip('这是我的Electron应用')
// 托盘图标点击事件
tray.on('click', () => {
if (mainWindow.isVisible()) {
mainWindow.hide()
} else {
mainWindow.show()
}
})

// 监听应用退出事件
app.on('before-quit', () => {
app.isQuiting = true
if (tray) {
// 销毁托盘图标
tray.destroy()
}
})

自定义菜单栏

设置应用菜单

如果无边框窗口需要设置自定义菜单栏,需要封装组件通过进程通信来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { app, Menu } from 'electron'

// 创建菜单模板
const contextMenu = Menu.buildFromTemplate([
{
label: '显示应用',
click: () => {
// 显示主窗口
mainWindow.show()
}
},
{
label: '退出',
click: () => {
app.quit()
}
}
])

// 设置应用菜单
Menu.setApplicationMenu(menu)

右键菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Menu } from 'electron'

// 创建菜单模板
const contextMenu = Menu.buildFromTemplate([
{
label: 'f12',
click: () => {
// 显示主窗口
mainWindow.webContents.openDevTools()
}
},
])
// 监听右键点击事件
mainWindow.webContents.on('context-menu', (event) => {
contextMenu.popup({ window: mainWindow })
})