分享electron多窗口实践

在当今的软件开发领域,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

相关推荐
编程猪猪侠1 天前
解决yarn install 报错 error \node_modules\electron: Command failed.
前端·javascript·electron
zooooooooy2 天前
Electron打包ARM环境deb包
后端·electron
red润3 天前
浏览器离屏渲染 vs. Electron离屏渲染——核心区别与应用场景
前端·electron·canvas
OpenIM4 天前
Electron Demo 的快速编译与启动
前端·javascript·electron
柚子a4 天前
Electron主进程渲染进程间通信的方式
electron
柚子a4 天前
electron使用remote报错
electron
DevUI团队5 天前
Electron 入门学习指南:快速搭建跨平台桌面应用
前端·javascript·electron
RedHood5 天前
鸿蒙投屏实现
electron·harmonyos
黑金IT6 天前
如何在 Electron 应用中安全地进行主进程与渲染器进程通信
服务器·安全·electron
培根芝士6 天前
Electron打包支持多语言
前端·javascript·electron