在当今的软件开发领域,Electron框架以其独特的能力脱颖而出,它允许开发者使用Web技术构建跨平台的桌面应用程序。随着Electron应用的复杂性增加,多窗口管理和窗口间通信的需求也日益增长。本文将深入探讨如何在Electron中创建和管理多个窗口,并实现它们之间的有效通信。我们将从基础概念出发,逐步深入到实际代码实现,确保您能够理解并应用这些技术,以构建更加丰富和互动的桌面应用体验。让我们开始这段技术探索之旅,揭开Electron多窗口应用开发的神秘面纱。
创建并打开新窗口
javascript
const { BrowserWindow, app, ipcMain} = require('electron')
const electron = require('electron')
const url = require('url')
const path = require('path')
var window2 = null
exports.getWindow = () => {
return window2
}
exports.launch = () => {
/// 实现在特定窗口打开新窗口
// id:与显示器相关联的唯一标识符(Integer)。
// rotation:屏幕旋转的角度,可以是 0, 1, 2, 3,分别代表 0°, 90°, 180°, 270°(Integer)。
// scaleFactor:输出设备的像素比例因子(Number)。
// touchSupport:是否支持触摸,可能的值为 available, unavailable, unknown(String)。
// bounds:包含显示器的边界矩形(Object)。
// size:包含显示器的宽度和高度(Object)。
// workArea:包含显示器的工作区域矩形(Object)。
// workAreaSize:包含显示器工作区域的宽度和高度(Object)。
var displays = electron.screen.getAllDisplays()
// 主要使用到bounds 中的x和y参数
// x:显示器左上角的横坐标(水平位置),以像素为单位。
// y:显示器左上角的纵坐标(垂直位置),以像素为单位。x
// 这些属性共同定义了显示器在屏幕上的确切位置和尺寸。例如,如果你有两个横向并排的显示器,主显示器(通常是0号显示器)的 bounds 可能看起来像这样:
// {
// x: 0,
// y: 0,
// width: 1920,
// height: 1080
// }
// 第二个显示器(1号显示器)的 bounds 可能看起来像这样:
// {
// x: 1920, // 假设两个显示器宽度都是1920像素
// y: 0,
// width: 1920,
// height: 1080
// }
var externalDisplay = displays.find((display) => {
return display.bounds.x !== 0 || display.bounds.y !== 0
})
/// 用x和y 确保窗口在第二个屏幕中打开
var x = externalDisplay ? externalDisplay.bounds.x + 10 : 0
var y = externalDisplay ? externalDisplay.bounds.y + 10 : 0
const windowOptions = {
width: 1000,
height: 1000,
x: x,
y: y
}
window2 = new BrowserWindow(windowOptions)
if (global.debug) {
window2.loadURL('http://localhost:8080/#/window2')
} else {
// 生产模式中,path.join('file://', __dirname, '../dist/index.html#/window2?query=xx') 指定路由不生效,携带的参数也会丢失,统一重定向到首页。
// 故使用以下方式构建路由
const cUrl = url.format({
protocol: 'file', // 指定 URL 的协议部分。在这个例子中,protocol 被设置为 'file',表示这是一个文件 URL
slashes: true, // 指示在协议之后是否添加双斜杠(//)
pathname: path.join(__dirname, '../dist/index.html'), // 指定 URL 的路径部分
hash: 'window2', // 指定 URL 的片段标识符(hash),也就是 URL 中 # 后面的部分
query: {
query: 'xx'
}
})
window2.loadURL(cUrl) // 将url加载到window2的实例中
}
window2.show() // 显示窗口
}
窗口间的通信
由于没有直接的方法可以使用ipcMain和ipcRender模块在Electron中的渲染进 程之间发送消息。electron官方推荐2种方案进行进程间的通信
- 将主进程作为渲染器之间的消息代理。 这需要将消息从一个渲染器发送到主进程,然后主进程将消息转发到另一个渲染器。
- 优点
- 安全性:由于渲染进程之间的通信需要通过主进程,这样可以更好地控制和监控跨渲染进程的通信,提高安全性。
- 控制性:主进程可以对消息进行审核和过滤,避免潜在的不安全操作或者错误数据的传递
- 缺点
- 性能开销:每次渲染进程之间的通信都需要经过主进程,这会增加通信的延迟和性能开销
- 复杂性:增加了主进程的负担,需要处理更多的消息转发逻辑,可能导致代码复杂度增加
- 优点
- 从主进程将一个 MessagePort 传递到两个渲染器。 这将允许在初始设置后渲染器之间直接进行通信。
- 优点
- 直接通信:渲染进程之间可以直接通过 MessagePort 进行通信,减少了通信的中间环节,提高了效率。
- 减少主进程负担:不需要主进程介入每次通信,减轻了主进程的负担,降低了主进程的复杂性。
- 缺点
- 安全性降低:直接通信可能会增加安全风险,因为主进程无法监控和控制渲染进程之间的通信内容。
- 调试难度:如果渲染进程之间的通信出现问题,调试可能会更加困难,因为没有主进程作为中间人来记录和监控通信过程。
- 优点
由于业务对安全性的要求,故选择使用第一种主进程代理的方案实现渲染进程间的通信。以下是具体实现
window1.vue
javascript
function sendMessage () {
window.electronAPI.sendMessage(message) // 窗口1向主进程发送信息
}
window2.vue
javascript
const ipc = window.require('electron').ipcRenderer
export default {
// ...
created () {
// 窗口2监听来自主线程的主屏信息
ipc.on(
'message-from-window1',
(message) => {
// 获取到了主屏发送的message
}
)
}
}
preload.js
javascript
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
sendMessage: (message) => ipcRenderer.send('send-message', message) // 暴露sendMessage方法
})
main.js
javascript
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('node:path')
const window2 = require('./window2')
// ...
function handleSendMessage (event, message) {
window2.webContents.send('message-from-window1', message) // 主线程发送主屏信息给窗口2
}
function createWindow () {
const Window1 = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
Window1.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.on('send-message', handleSendMessage) // 主线程监听并接收主屏信息
createWindow()
})
-from manta