4
头图

本期内容主要介绍拓展屏的开启以及两个窗口之间的消息通信。

拓展屏

在窗口启动这一节里我们封装了一个createWindow,用这个来创建一个窗口,拓展屏也是窗口,只不过我们根据有几个显示屏把其放在对应的外接屏上罢了。

渲染进程

我们在渲染进程添加一个按钮,让其点击之后,向主进程发送对拓展屏的操作(打开或隐藏)

<a-button type="primary" @click="openScreen">打开拓展屏</a-button>

const state = reactive({
  open: true,
  message: ''
})
async function openScreen() {
  await window.ipcRenderer.invoke('win-subScreen', {
    open: state.open,
    path: '#/subScreen'
  })
  state.open = !state.open
}

主进程

主进程接受到之后,通过screen.getAllDisplays()获取现在窗口的数组(有几个显示屏),然后根据externalDisplay来获取我们外接屏幕的信息。
同样的,我们创建新的窗口也是用createWindow,之前我们渲染进程传递了一个path,这个path呢就是我们拓展屏要展示的页面。
这里需要注意的是createWindow返回的值需要用一个全局变量保存起来,之前托盘的时候说过,如果是局部变量的话,在函数执行完毕之后会被销毁,那么窗口也就被销毁了。
我们这里用global赋值是后面窗口通信会用到,你这里用全局变量赋值时一样的。

global.js

global.tray = null
global.willQuitApp = false
global.envConfig = {}
global.sharedObject = {
  win: '',
  subScreen: ''
}

export default global


import { ipcMain, app, screen } from 'electron'
import global from '../config/global'
import createWindow from './createWindow'
import path from 'path'

const win = global.sharedObject.win
ipcMain.handle('win-subScreen', (_, data) => {
  if (data.open) {
    const displays = screen.getAllDisplays()
    const mainBounds = win.getNormalBounds()
    const externalDisplay = displays.find((display) => {
      return display.bounds.x !== 0 || display.bounds.y !== 0
    })
    if (externalDisplay) {
      if (global.sharedObject.subScreen) {
        global.sharedObject.subScreen.show()
      } else {
        global.sharedObject.subScreen = createWindow({
          frame: false,
          show: false,
          parent: win, // win是主窗口
          fullscreen: true,
          webPreferences: {
            webSecurity: false,
            contextIsolation: false,
            enableRemoteModule: true,
            nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
            plugins: true,
            preload: path.join(__dirname, 'preload.js'),
            devTools: false
          },
          x: mainBounds.x < 0 && Math.abs(mainBounds.x) > (win.getContentSize()[0] / 2) ? 0 : externalDisplay.bounds.x,
          y: externalDisplay.bounds.y
        }, data.path, `index.html${data.path}`)
        global.sharedObject.subScreen.once('ready-to-show', () => {
          global.sharedObject.subScreen.show()
        })
        global.sharedObject.subScreen.on('closed', () => {
          global.sharedObject.subScreen = null
        })
      }
    } else {
      console.log('未检测到拓展屏')
    }
  } else {
    global.sharedObject.subScreen && global.sharedObject.subScreen.destroy()
  }
})

这里呢有个小处理,我们先判断一下我们的软件是位于那个屏幕,比如我们有两个屏,1和2,那么我们的主窗口和拓展屏应该处于不同位置,比如:我们的主窗口在1的话,那么我们的拓展屏应该在2打开,如果位置相同的话,由于我们拓展屏设置的是全屏,此时会把主窗口给遮挡住,如果没有设置快捷键关闭的话,那么将无法关闭拓展屏,故有以下处理。

const mainBounds = win.getNormalBounds()
mainBounds为主窗口信息,如果主窗口有一半以上都处于副屏幕的话,那么我们认为主窗口在副屏,那么拓展屏打开位置应该在主屏,否则的话应该在副屏。
x: mainBounds.x < 0 && Math.abs(mainBounds.x) > (win.getContentSize()[0] / 2) ? 0 : externalDisplay.bounds.x

窗口通信

electorn的窗口间的通信呢,一般来说有两种方式。

  1. 窗口A渲染进程发送信息到主进程,主进程收到后发送信息给窗口B的渲染进程,也就是用主进程做个中转。
  2. 窗口A渲染进程中通过窗口B的WebContents.id直接发送信息给窗口B。

通信方式

这里介绍几种常见的通信方式

ipcRenderer.invoke:渲染进程发送消息给主进程,这是一个promise,可以在其resolve中获取ipcMain.handle的返回值
ipcMain.handle:接收invoke发送的信息,可以return值给ipcRenderer.invoke

ipcRenderer.send:渲染进程发送消息给主进程
ipcMain.on:接收send的信息

ipcRenderer.on:接收主进程的消息
webContents.send:主进程发送消息给渲染进程

ipcRenderer.sendTo:可以通过webContentsId直接发送信息到对应的渲染进程窗口

我们这里把1,2都实现一下

渲染进程

渲染进程中可以通过remote来获取主进程中global的值,remote.getGlobal('sharedObject').subScreen就是我们之前拓展屏的窗口。
transferSub是我们的方案1,directSub是方案2。

div class="subMessage">
  <a-textarea
    v-model:value="state.message"
    :auto-size="{ minRows: 2, maxRows: 5 }"
  />
  <a-button type="primary" :disabled="state.message.length === 0" @click="transferSub">中转发送</a-button>
  <a-button type="primary" :disabled="state.message.length === 0" @click="directSub">直接发送</a-button>
</div>

import { defineComponent, reactive, onMounted, onUnmounted, getCurrentInstance } from 'vue'
const { remote } = require('electron')

const state = reactive({
  open: true,
  message: ''
})
const { proxy } = getCurrentInstance()
const { $message } = proxy
function transferSub() {
  window.ipcRenderer.invoke('win-subScreen-message', state.message)
}
function directSub() {
  const subScreen = remote.getGlobal('sharedObject').subScreen
  if (subScreen) {
    window.ipcRenderer.sendTo(subScreen.webContents.id, 'main-subScree', state.message)
  }
}
onMounted(() => {
  window.ipcRenderer.on('subScree-main', (_event, data) => {
    $message.success(data)
  })
})
onUnmounted(() => {
  window.ipcRenderer.removeListener('subScree-main')
})

主进程

中转一下,用webContents.send把信息发送给拓展屏窗口

ipcMain.handle('win-subScreen-message', (_, data) => {
  if (global.sharedObject.subScreen) {
    global.sharedObject.subScreen.webContents.send('renderer-subScreen-message', data)
  }
})

拓展屏信息接收及发送

我们这里直接监听中转的消息以及直接发送的消息,再直接通知主窗口消息。

<template>
  <div class="subScreen">{{ state.message }}</div>
</template>

<script>
import { defineComponent, reactive, onMounted, onUnmounted } from 'vue'
const { remote } = require('electron')

export default defineComponent({
  setup() {
    const state = reactive({
      message: ''
    })
    onMounted(() => {
      const win = remote.getGlobal('sharedObject').win
      window.ipcRenderer.on('renderer-subScreen-message', (_event, data) => {
        state.message = data
        window.ipcRenderer.sendTo(win.webContents.id, 'subScree-main', '我收到了中转发送信息')
      })
      window.ipcRenderer.on('main-subScree', (_event, data) => {
        state.message = data
        window.ipcRenderer.sendTo(win.webContents.id, 'subScree-main', '我收到了直接发送信息')
      })
    })
    onUnmounted(() => {
      window.ipcRenderer.removeListener('renderer-subScreen-message')
      window.ipcRenderer.removeListener('main-subScree')
    })
    return {
      state
    }
  }
})
</script>

验证

我们打开拓展屏,在主窗口输入信息,点击直接发送或者中转发送,看看拓展窗口的显示是否为我们输入的信息,主窗口展示拓展屏的通知信息。

本系列更新只有利用周末和下班时间整理,比较多的内容的话更新会比较慢,希望能对你有所帮助,请多多star或点赞收藏支持一下

本文地址:https://xuxin123.com/electron/ipc-subscreen
本文github地址:链接


陌路凡歌
7.9k 声望258 粉丝

人笨,多听,多写,多看书