Electron 进程通信

渲染进程到主进程(单项)

主要使用的api如下:

ipcMain.on

ipcRenderer.send

我们一般使用web界面,向主进程进行发送信息。

demo如下:

写一个将程序窗口标题设置为index.html标题的功能:

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
const {BrowserWindow,app,ipcMain} = require("electron")
const path = require("path")

function createWin(){
const win = new BrowserWindow({
width:200,
height:200,
webPreferences:{
preload: path.join(__dirname,"preload.js")
}
})

win.loadFile("index.html")
win.webContents.openDevTools()
// 接收渲染进程的事件
ipcMain.on("set-title",(event,value)=>{
console.log(event.sender,value)
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(value)
})
}


app.whenReady().then(()=>{
createWin()

app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})

preload.js

预加载脚本,沟通主进程和渲染进程的桥梁

1
2
3
4
5
6
7
const {contextBridge,ipcRenderer} = require("electron")

// 通过exposeInMainWorld将electronAPI挂载到渲染进程的Window中方便调用
contextBridge.exposeInMainWorld("electronAPI",{
setTitle:(title)=>ipcRenderer.send("set-title",title)
})

没错上文的ipcRenderer.send就是把事件发给主进程

index.html && render.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenthahah</title>
</head>
<body>
<button id="go">改变标题</button>
<script src="render.js"></script>
</body>
</html>


---render.js
const btn = document.getElementById("go")
btn.addEventListener("click",()=>{
console.log("运行了")
window.electronAPI.setTitle("原地起飞")
})

这样子就实现了一个我点击按钮以后窗口标题变更的功能。

image-20241213162223384

主进程与渲染进程通信

使用的主要api

WebContents

main.js

整个菜单,菜单后面说,这个是我直接copy的官方案例,

mainWindow.webContents.send

注意这个位置,这个位置声明了是哪个窗口,这个操作窗口是主窗口webContents的send

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
const { app, BrowserWindow, Menu, ipcMain } = require('electron/main')
const path = require('node:path')

function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

const menu = Menu.buildFromTemplate([
{
label: app.name,
submenu: [
{
click: () => mainWindow.webContents.send('update-counter', 1),
label: 'Increment'
},
{
click: () => mainWindow.webContents.send('update-counter', -1),
label: 'Decrement'
}
]
}

])

Menu.setApplicationMenu(menu)
mainWindow.loadFile('index.html')

// Open the DevTools.
mainWindow.webContents.openDevTools()
}

app.whenReady().then(() => {
ipcMain.on('counter-value', (_event, value) => {
console.log(value) // will print value to Node console
})
createWindow()

app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})

依旧是预渲染脚本

preload.js

这次暴露两个接口,因为是主进程通信渲染接口,所以中间值使用了ipcRenderer.on监听主进程的传值:主进程的核心代码在这里:mainWindow.webContents.send('update-counter', 1)会给一个1,刚刚好对应的就是运行的函数(_event, value) => callback(value)并且闭包接收一个callback,此时这个callback,就可以在对应的render定义行为了。

1
2
3
4
5
6
const { contextBridge, ipcRenderer } = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
counterValue: (value) => ipcRenderer.send('counter-value', value)
})

render.js

1
2
3
4
5
6
7
8
const counter = document.getElementById('counter')

window.electronAPI.onUpdateCounter((value) => {
const oldValue = Number(counter.innerText)
const newValue = oldValue + value
counter.innerText = newValue.toString()
window.electronAPI.counterValue(newValue)
})

主进程和渲染进程双向通信

  1. 首先关于双向通信,其实可以把上面的两种方案结合起来,上面的两种方案结合起来。
  2. 使用进程通信给予的:
    1. ipcRenderer.invoke
    2. ipcMain.handle

融会贯通写法

我们来实践一下方法一:

也就是融会之前所学的ipcRenderer.on 和 ipcRenderer.send进行操作。

我们写了一个关于index.html,一个按钮用来改变窗口标题。

我们要经过renderer发送消息让main.js进行改变标题,改变完以后main.js要告诉renderder完成了。

具体代码如下:

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenthahah</title>
</head>
<body>
<button id="go">改变标题</button>
<p id="status"></p>
<script src="render.js"></script>
</body>
</html>

render.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
window.addEventListener("DOMContentLoaded",()=>{
const btn = document.getElementById("go")
const status = document.getElementById("status")
btn.addEventListener("click",()=>{
console.log("运行了")
window.electronAPI.setTitle("原地起飞")
})

// 这样子就进行监听了
window.mainToRenderer.res((res)=>{
status.innerText = res
})
})

preload.js

1
2
3
4
5
6
7
8
9
10
11
12
const {contextBridge,ipcRenderer} = require("electron")


contextBridge.exposeInMainWorld("electronAPI",{
setTitle:(title)=>ipcRenderer.send("set-title",title)
})

contextBridge.exposeInMainWorld("mainToRenderer",{
res:(callback) => ipcRenderer.on("msg",(_event,value)=>{
callback(value)
})
})

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
const {BrowserWindow,app,ipcMain} = require("electron")
const path = require("path")

function createWin(){
const win = new BrowserWindow({
width:200,
height:200,
webPreferences:{
preload: path.join(__dirname,"preload.js")
}
})

win.loadFile("index.html")
win.webContents.openDevTools()
ipcMain.on("set-title",(event,value)=>{
console.log(event.sender,value)
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(value)
BrowserWindow.fromWebContents(event.sender).send('msg','已经收到通知')
})
}


app.whenReady().then(()=>{
createWin()

app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})


app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})

官方使用invoke,handle写法

这个部分官方的案例写的非常明白,所以我就不多说了,在部分代码中我写了注释

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
const { app, BrowserWindow, ipcMain, dialog } = require('electron/main')
const path = require('node:path')

async function handleFileOpen () {
const { canceled, filePaths } = await dialog.showOpenDialog()
if (!canceled) {
return filePaths[0]
}
}

function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
ipcMain.handle('dialog:openFile', handleFileOpen)
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})

preload.js

1
2
3
4
5
const { contextBridge, ipcRenderer } = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile')
})

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Dialog</title>
</head>
<body>
<button type="button" id="btn">Open a File</button>
File path: <strong id="filePath"></strong>
<script src='./renderer.js'></script>
</body>
</html>

renderer.js

1
2
3
4
5
6
7
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => {
const filePath = await window.electronAPI.openFile()
filePathElement.innerText = filePath
})