文章目录
一、Electron 进程之间的通信
Electron进程通信大致可分为主进程和渲染进程两种:
- 主进程:指的就是 node.js 进程,node.js 只有一个进程。
- 渲染进程:可以简单理解为一个 Chromium 的 web 页面,可以有多个渲染进程。
ipc通信就是在主进程和渲染进程之间通信
推荐使用上下文隔离渲染器进程进行通信。这种方式的好处是无需在渲染进程中直接使用 ipcRenderer 发送消息,当我们使用 vue 或者其他前端框架开发界面时,上下文隔离方式使用起来更加方便,基本上感受不到 electron 对前端框架的影响。
二、上下文隔离的进程间通信方式有四种,示例如下
示例背景如下
在vue项目中创建一个electron文件夹
- main.js(主进程文件)
- preload.js(预加载脚本)
1、 渲染器到主进程(单向)
ipcMain.on 和 ipcRenderer.send
示例描述:从 渲染进程中操作主进程的关闭操作,也就是从渲染进程中关闭electron
(1)主进程:electron/main.js
const { ipcMain } = require('electron');
ipcMain.on('operation-window',function(event,operationType){
switch(operationType){
case "max"://窗口 最大化
win.maximize()
break;
case 'restoreDown'://窗口 向下还原
win.unmaximize()
break;
case "min"://窗口 最小化
win.minimize()
break;
case 'close'://窗口 关闭
win.close()
break;
}
})
(2)预加载文件:electron/preload.js
//向渲染器进程暴露一个全局的 window.electronAPI 变量。
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
operationWindow: (operationType) => ipcRenderer.send('operation-window', operationType),
})
(3)渲染进程文件:xxx.vue
<div>
<button @click="onClickClose">关闭窗口</button>
</div>
<script lang="ts" setup>
const onClickClose = () => {
if(window.electronAPI && window.electronAPI.operationWindow){
window.electronAPI.operationWindow('close')
}
}
</script>
2、渲染器进程到主进程(双向)
ipcMain.handle 和 ipcRenderer.invoke
示例描述:从渲染进程中 调用 主进程的弹窗选择文件后返回文件路径给 渲染进程
- 在主进程中,我们将创建一个 handleFileOpen() 函数,它调用electron的 dialog.showOpenDialog 并返回用户选择的文件路径值。
- 每当渲染器进程通过 dialog:openFile 通道发送 ipcRender.invoke 消息时,此函数被用作一个回调。然后,返回值将作为一个 Promise 返回到最初的 invoke 调用。
(1)主进程:electron/main.js
const { ipcMain,dialog } = require('electron');
async function handleFileOpen() {
const { canceled, filePaths } = await dialog.showOpenDialog()//调用electron的打开文件弹窗
if (canceled) {
return
} else {
return filePaths[0] // 返回文件名给渲染进程
}
}
ipcMain.handle('dialog:openFile',handleFileOpen)
(2)预加载文件:electron/preload.js
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile')
})
(3)渲染进程文件:xxx.vue
<div>
<button @click="onSelectFile">选择文件</button>
</div>
<script lang="ts" setup>
const onSelectFile = async () => {
if(window.electronAPI && window.electronAPI.openFile){
const filePath = await window.electronAPI.openFile()
console.log("选择的文件路径是:" + filePath)
}
}
</script>
3、主进程到渲染器进程(双向)
win.webContents.send 和 ipcRenderer.on
event.sender.send 和 ipcMain.on
示例描述:从主进程的托盘上下文菜单每次被点击都通知 渲染进程, 渲染进程接收后,告诉主进程收到了。
- 将消息从主进程发送到渲染器进程时,需要指定是哪一个渲染器接收消息。
- 消息需要通过其 WebContents 实例发送到渲染器进程。此 WebContents 实例包含一个 send 方法,其使用方式与 ipcRenderer.send 相同。
(1) 主进程通知渲染进程【win.webContents.send 和 ipcRenderer.on】
① 主进程:electron/main.js
const { ipcMain, Tray, Menu } = require('electron');
//系统托盘
let appTray = new Tray(iconPath)
const trayContextMenu = Menu.buildFromTemplate([
{
label: "打开安信",
click:() => {
win.webContents.send('update-counter',1)//每次点击都告诉渲染进程,我被点击了一次
}
},
])
//系统托盘的提示文本
appTray.setToolTip('安信');
//点击系统托盘打开窗口
appTray.on('click',() => {
win.show();
});
//系统托盘右键点击事件
appTray.on('right-click',() => {
appTray.popUpContextMenu(trayContextMenu);//显示系统菜单
});
② 预加载文件:electron/preload.js
//向渲染器进程暴露一个全局的 window.electronAPI 变量。
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
onUpdateCounter: (callback) => ipcRenderer.on('update-counter', callback);
})
③ 渲染进程文件:xxx.vue
<script lang="ts" setup>
let total = 0;
window.electronAPI.onUpdateCounter((event, value) => {
console.log("event---",event)
console.log("value---",value)
total += value
})
</script>
(2)如果渲染进程收到消息后想要回复主进程【event.sender.send 和 ipcMain.on】
① 渲染进程文件:xxx.vue
//在渲染器进程中,使用 event 参数,通过 counter-value 通道将回复发送回主进程。
<script lang="ts" setup>
let total = 0;
window.electronAPI.onUpdateCounter((event, value) => {
console.log("event---",event)
console.log("value---",value)
total += value
event.sender.send('counter-value', '累计点击次数是:',total) // 发送消息到主进程
})
</script>
② 主进程:electron/main.js
//在主进程中,监听 counter-value 事件并适当地处理它们。
const { ipcMain} = require('electron');
ipcMain.on('counter-value',function(event,value){
console.log("接收渲染进程发过来的消息===",value)
})
4、渲染器进程到渲染器进程
没有直接的方法可以使用 ipcMain 和 ipcRenderer 模块在 Electron 中的渲染器进程之间发送消息。为此,我们有两种选择:
(1) 将主进程作为渲染器之间的消息代理。这需要将消息从一个渲染器发送到主进程,然后主进程将消息转发到另一个渲染器。
主进程 代码
/**
* 接收渲染进程 转发消息 的通知
* @param {Object} event
* @param {String} windowName 目标窗口名称
* @param {Array} params 发送参数
*/
ipcMain.on('post-message',function(event,windowName,params) {
if(windows[windowName]){
windows[windowName].webContents.send('get-message',params);//转发给指定窗口
}
})
预加载文件 代码
//向渲染器进程暴露一个全局的 window.electronAPI 变量。
const { contextBridge, ipcRenderer, app } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
postMessage: (windowName,params) => ipcRenderer.send('post-message',windowName,params),//跨窗口发送消息,接收渲染进程的转发消息的通知
getMessage: (callback) => ipcRenderer.on('get-message', callback),//把消息通知到指定窗口
})
渲染进程1 代码 (发送)
if(window.electronAPI && window.electronAPI.postMessage){
window.electronAPI.postMessage('childWindow',{test:"发送给子窗口的数据~~"});
}
渲染进程2 代码 (接收)
if(window.electronAPI && window.electronAPI.getMessage){
window.electronAPI.getMessage((event,params) => {
console.log(params)//打印结果:{test:"发送给子窗口的数据~~"}
})
}
(2) 从主进程将一个 MessagePort 传递到两个渲染器。这将允许在初始设置后渲染器之间直接进行通信。