Skip to content

窗口间通讯那些事

文本主要讲解了如何使用uTools创建一个窗口,并且讲解了插件如何与创建的这个窗口进行通讯

electron的支持

众所周知,uTools是基于electron构建的应用,所以,技术上还是通过electron的API来实现。

首先,我们先前往electron的ipcRenderer文档, 可以看到,ipcRenderer提供了很多接口:

  • on
  • off
  • once
  • addListener
  • removeListener
  • removeAllListeners
  • send
  • sendTo
  • invoke
  • sendSync
  • postMessage
  • sendToHost

其中,重点的就是sendTo接口与on接口,sendTo可以将数据发送到指定的窗口指定通道中,on可以接收当前窗口指定通道的消息

javascript
ipcRenderer.sendTo(id, channel, message);

ipcRenderer.on(channel, message => {
    console.log('接收到消息:', message);
});

uTools的支持

uTools提供了一个API:createBrowserWindow 用于创建一个窗口,用法为:

typescript
const ubWindow = utools.createBrowserWindow(
  // 创建窗口的html文件,路径是相对于plugin.json的相对路径
  'sub.html',
  {
    // 用户是否可以自定义宽高
    useContentSize: true,
    // 是否隐藏任务栏图标
    skipTaskbar: false,
    // 初始宽度
    width: 500,
    // 初始高度
    height: 100,
    // 窗口是否置顶
    alwaysOnTop: false,
    // 是否显示窗口的框架,如果fasle,就可以自己定义最小化、最大化和关闭按钮
    frame: true,
    // 窗口是否透明
    transparent: false,
    // 窗口的背景颜色
    backgroundColor: '#000000',
    // 是否有阴影
    hasShadow: false,
    webPreferences: {
      // 这个窗口的preload.js脚本,也是相对于plugin.json的相对路径
      preload: 'javascript/sub.js'
    }
  }, () => {
    // 窗口创建成功回调
    try {
      // 显示窗口
      ubWindow.show();
      // 判断是否是dev环境
      if (utools.isDev()) {
        // 如果是dev环境,打开开发者工具
        ubWindow.webContents.openDevTools();
      }
      // 隐藏主窗口
    } catch (e) {
      console.error("打开子窗口失败", e);
    }
  });

实战

有了上面的基础,我们就很容易实现窗口通讯了

第一步

创建preload.js文件,并在plugin.json中指定preload.js文件

json
{
  "preload": "preload.js"
}

第二步

preload.js中,编写发送消息和接收消息的方法

javascript
const {ipcRenderer} = require('electron');

/**
 * 发送消息到子窗口
 * @param id {string} 窗口ID
 * @param channel {string} 管道
 * @param msg {string} 消息
 */
function sendMsgTo(id, channel, msg) {
    ipcRenderer.sendTo(id, channel, msg)
}

/**
 * 接收子窗口发过来的消息
 * @param callback {(msg: string) => void} 接收消息回调
 */
function receiveMsg(channel, callback) {
    ipcRenderer.on(channel, (_event, res) => {
        callback(res);
    })
}

window.preload = {
    sendMsgTo, receiveMsg
}

第三步

创建子窗口sub.htmlsub.js

javascript
const { ipcRenderer } = require('electron')
// 插件的窗口ID
let parentId = null;
// 管道名称,可以自定义,只需要和发送时的管道名称一致即可
const channel =  'channel'

window.preload = {
    /***  接收主窗口发送过来的消息  ***/
    receiveMsg: (callback) => {
        ipcRenderer.on(channel, (event, res) => {
            // 保存插件的窗口ID
            parentId = event.senderId;
            if (res) {
                callback(res);
            }
        })
    },
    /***  向插件主窗口发送消息  ***/
    sendMsg: (msg) => {
        if (parentId) {
            ipcRenderer.sendTo(parentId, channel, msg);
        }
    }
}
html
<!doctype html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>子窗口</title>
</head>
<body>

</body>
<script>
  window.preload.receiveMsg(msg => {
      console.log('接收插件发送的消息:', msg);
    // 发送消息到插件
      window.preload.sendMsg('发送给插件的消息')
  })
</script>
</html>

第四步

在插件中创建子窗口,并发送消息

typescript
// 与子窗口保持一致
const channel = 'channel';
const ubWindow = utools.createBrowserWindow(
  // 创建窗口的html文件,路径是相对于plugin.json的相对路径
  'sub.html',
  {
    // 用户是否可以自定义宽高
    useContentSize: true,
    // 是否隐藏任务栏图标
    skipTaskbar: false,
    // 初始宽度
    width: 500,
    // 初始高度
    height: 100,
    // 窗口是否置顶
    alwaysOnTop: false,
    // 是否显示窗口的框架,如果fasle,就可以自己定义最小化、最大化和关闭按钮
    frame: true,
    // 窗口是否透明
    transparent: false,
    // 窗口的背景颜色
    backgroundColor: '#000000',
    // 是否有阴影
    hasShadow: false,
    webPreferences: {
      // 这个窗口的preload.js脚本,也是相对于plugin.json的相对路径
      preload: 'javascript/sub.js'
    }
  }, () => {
    // 窗口创建成功回调
    try {
      // 显示窗口
      ubWindow.show();
      
      window.preload.sendMsgTo(this.ubWindow.webContents.id, channel, 
        '插件发送消息给主窗口'
      );
      window.preload.receiveMsg(msg => {
        console.log('接收到子窗口发送的消息', msg);
      })
      // 判断是否是dev环境
      if (utools.isDev()) {
        // 如果是dev环境,打开开发者工具
        ubWindow.webContents.openDevTools();
      }
      // 隐藏主窗口
    } catch (e) {
      console.error("打开子窗口失败", e);
    }
  });

至此,窗口间通讯就完成了

注意

  1. 发送的消息可以是任意对象,例子中只是使用了字符串,并不是只能发送字符串
  2. 发送的对象需要可序列化,例如vue的ref包装的对象就是不可序列化的,发送会报错
  3. 管道可以使用多个,不同的功能可以使用不同的管道,只需要发送和监听的管道一致即可,例子中只是发送了一个进行举例