Electron 子窗口创建和通信

960 阅读4分钟

electron中创建桌面端应用时,根据功能需求会创建弹窗,并且可以在隐藏主界面后子弹窗依旧存在。遇到这类情况,可以重新使用electron创建子窗口。

在 Electron 应用程序中,子弹窗(或称为“弹出窗口”)通常是指在主窗口之上打开的额外小窗口。这些窗口可以用于显示辅助信息、对话框、工具面板等。 子窗口一般用于显示辅助内容或执行任务,子窗口也是通过 new BrowserWindow进行创建。

下面对子窗口的创建和父子窗口的通信进行介绍,以项目为例子进行说明:

时间沙漏 -- 番茄钟

我正在做一个时间管理工具准备来对electron进行练手,页面仿照时光序,里面有一项是番茄钟功能

此界面在创建好番茄钟后,开始启动的番茄钟界面准备出现倒计时弹窗,并且弹窗需要可拖动。 如果是在主窗口中创建,弹窗就不能拖动出主窗口,所以需要创建一个子窗口来承载该倒计时弹窗。

主窗口

p2.png

倒计时窗口

p3.png

创建子弹窗

在主窗口点击开始番茄钟后,通过window.electronAPI.sqliteTomato传递相应信息到后台进程中。

为了使用方便,我在preload.js封装下ipcRenderer各类方法:

// 在渲染进行中不直接使用ipcRenderer
contextBridge.exposeInMainWorld("electronAPI", {
  removeAllListeners: (name) => ipcRenderer.removeAllListeners(name),
  ipcRendererSend: (ipcname, content) => ipcRenderer.send(ipcname, content),
  ipcRendererOn: (ipcname, callback) => ipcRenderer.on(ipcname, callback),
  sqliteTomato: (args) => ipcRenderer.invoke('sqlite-tomato', args),
  sqliteTomatoNumber: (nums, callback) => ipcRenderer.on(`sqlite-tomato-${ nums }`, callback),
  sqliteTomatoOnceNumber: (nums, callback) => ipcRenderer.once(`sqlite-tomato-${ nums }`, callback),
}); 

创建需要挂载到子弹窗上的页面countTomato.vue,在vue-routerindex.js中添加该路径

p5.png

然后定义好创建子窗口的方法,并且根据开发环境判断loadURL加载的页面地址

  • 参数分别为路径,窗口大小,传入信息
    1. url: vue-router的路由名称,在下方loadURL加载时使用,传入/countTomato
    2. size:窗口大小,里面存在wh
    3. arg:本来是要放一些从主窗口传进的信息的,目前暂时没有使用
  • 如果childWindow存在,就不再创建新弹窗
  • 根据我自己的需求,我隐藏了顶部栏frame,并设置不可改变大小resizeable,背景透明transparent
const isDevelopment = !app.isPackaged, childWindow = null;
// 创建子弹窗函数
const createChildWindow = (url, size, arg)=>{
    if (childWindow) { return ; }
    let { w, h } = size;
    // 实例化BrowserWindow对象
    childWindow = new BrowserWindow({
        width: w,
        height: h,
        frame: false,  // 顶部栏隐藏
        resizable: true,  // 不能改变大小
        transparent: true,  // 背景透明
        icon: path.join(__dirname, '../image/package.png'),
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'),
            // 书写渲染进程中的配置
            nodeIntegration: true,
            contextIsolation: true, 
        }
    })
	// 判断是否在开发环境调试
    let winUrl = isDevelopment ? 'http://localhost:5522' : `file://${path.resolve(__dirname, '../../')}/dist/index.html`;
    childWindow.loadURL(`${winUrl}#${url}`);
}

如果弹窗出现,但是页面空调,可以看winUrl路径是否正确

子弹窗拖拽

因为没有使用默认的顶部栏,当前弹窗并不能进行拖拽,可以在页面上自定义一条顶部栏

p4.png

CSS样式中进行设置-webkit-app-region: drag;,因为electron使用chrome内核,所以拖拽直接生效

参考API:自定义窗口 | Electron (electronjs.org)

子窗口关闭

自定义的顶部栏上添加关闭最小化按钮,在进程文件中添加对应的监听方法

p6.png


当前界面效果已完成,可以生成窗口,并将页面挂在子窗口中,子窗口可以拖拽到主窗口外面。

p3.gif

多弹窗通信

在番茄钟功能里,两个窗口之间需要互相通信,主窗口将开启的番茄钟信息传给子窗口,子窗口完成一次番茄钟后,将增加的次数传回主窗口。

p7.png

在后台进行中设置监听和发送方法,根据窗口不同来进行监听

// 监听父子窗口通信
const connectWin = () => {
    // 接受父窗口发送的消息
    ipcMain.on('connect-father-window', async (event, arg) => {
        childWindow?.webContents.send('send-child', arg);
    })
    // 接受子窗口发送的消息
    ipcMain.on('connect-child-window', async (event, arg) => {
        mainWindow?.webContents.send('send-father', arg);
    })
}

页面上也添加相应的监听发送方法,以下是主窗口上的方法,子窗口的方法也是类似

// 发送给子窗口数据
window.electronAPI.ipcRendererSend('connect-father-window', JSON.stringify({ msg, data }));
// 接收子窗口数据
window.electronAPI.ipcRendererOn('send-father', (e, data) => {
	let infos = JSON.parse(data);
	switch (infos.msg) {
		case 'window-create-finish': ...; break;
		case 'tomato-finish': ...; break;
	}
})

PS: 这里需要注意,在创建子窗口时,主窗口发送信息过来,子窗口是接收不到的。所以需要在子窗口渲染结束后,必须由子窗口先发送数据告知主窗口,然后主窗口再发送信息。

初始化子窗口创建和后续通信流程如下:

p8.png

总体而言,这一部分还是比较简答,如果将思路打通,把方法封装下,做起来速度还是比较快的。