在前面的讲解中,我们已经用到了 Electron 的窗口以及它的一些初始化配置参数,基本知道了窗口就是用来承载 html 的容器。Electron 中主要使用 BrowserWindow 模块来完成窗口的建设,由于窗口都是独立的渲染进程,因此基本不会因为某一个窗口出现性能问题而影响到其他窗口。既然我们开发的是一个桌面应用,那么,窗口除了一些用于加载 html 的基础参数外,还应该具备哪些能力呢?本讲将为你详细介绍窗口的各种能力和应用场景。
在第 01 讲的 demo 演示里面,已经了解了可以通过 new BrowserWindow 创建一个新的窗口实例以及创建窗口时的常用参数,同时可以通过 ready-to-show 事件来判断窗口显示的时机,即等内容加载完成再显示,避免用户看到短暂的白屏,提升用户体验。
主进程中创建窗口
// main.js
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
show: false, // 窗口默认隐藏
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true // 允许渲染进程访问 nodejs api
// 等到窗口内容准备好了再显示出来,避免白屏
win.once('ready-to-show', () => {
win.show()
win.loadFile('dist/index.html')
注意里面窗口的宽高设置一定要设置成整数,否则不生效。比如实际应用中可能需要按屏幕百分比来设置,别忘了加上 parseInt 整型化。loadFile 是用来设置当前这个窗口加载的 html 路径的。可以看到这里是加载的本地文件,即 file 协议的。那么对于远端 url 网页能否加载呢?当然可以,调用 win 实例上的 loadUrl 方法即可(其实这里 Electron 也支持用 loadUrl 来加载 file 协议的路径)
win.loadUrl('https://www.baidu.com/')
一般情况,创建 Electron 应用时不管有多少个窗口,都会有一个主窗口来方便对整个应用进行控制,一般也是第一个打开的窗口。主窗口关闭也就意味着应用的退出。
渲染进程中创建窗口
当然我们也可以在渲染进程中通过 window.open 打开新的窗口,还可以借助 remote 模块新建 BrowserWindow 实例。remote 模块是为了方便在渲染进程直接使用一些主进程的模块而存在的,减少与主进程的通信。
// render.js 渲染进程中直接打开一个窗口
const remote = require("@electron/remote")
const BrowserWindow = remote.BrowserWindow
const win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('https://github.com')
常用实例属性
上面通过窗口实例调用了一些方法来完成各种功能,接下来我们看看这个窗口实例上还有哪些常用属性。
win.id:窗口 id,唯一标识
win.title:原生窗口标题,可以与 html 中的标题不一样
win.fullScreen:窗口当前是否是全屏状态
win.shadow:窗口是否显示阴影
win.minimizable:窗口能否被用户手动最小化
win.maximizable:窗口能否被用户手动最大化
win.resizable:窗口是否可以被用户手动调整大小,可以阻止用户缩放窗口
win.movable:窗口是否可被用户拖动
......
更多属性请参考 Electron 官网 。
创建窗口后,需要将窗口实例缓存下来,除了调用它们的 api 去完成工作外,还可以监听用户关闭窗口等事件完成特殊业务处理。
close 和 closed 事件分别在 窗口即将关闭和窗口关闭后触发
监听 close 事件后可以根据业务情况决定是否阻止本次窗口关闭,比如用户在未保存的情况误操作了关闭,这时,可以提醒用户,同时阻止窗口关闭。
closed 事件表示窗口已经关闭了,无法进行阻止,当收到 closed 事件后,应该删除窗口实例引用及其他相关的内容,如断开 websocket 连接等,甚至应该考虑在所有窗口都关闭后执行退出程序的逻辑。
上面我们说的主要是用户触发的关闭,如果我们调用实例的关闭方法由程序自动关闭窗口会怎么样?
通过 win.close() 可以自动关闭窗口,和用户手动关闭一样,也会触发 close 和 closed 事件,并能在 close 事件中阻止其关闭。
通过 win.destory() 可以强制关闭窗口,不会触发 close 事件,但会触发 closed 事件
窗口隐藏和恢复
win.hide()
隐藏窗口,隐藏窗口会触发隐藏事件'hide'和失焦事件'blur'。可以在'hide'事件中通过 preventDefault 阻止隐藏。
win.show()
将隐藏的窗口显示出来,窗口里面的内容不会有任何影响。同时会触发'show'事件。
如果需要在用户点击 'x' 的时候如 qq 音乐一样隐藏 app,而不是退出 app,我们就可以在 close 事件中阻止默认的关闭行为,同时通过 hide 来隐藏窗口。
窗口聚焦和失焦
在窗口初始化时可以通过参数 focusable:true 设置窗口是否允许聚焦。下面是操作焦点的 api。
win.focus()
对窗口聚焦或激活,一般在桌面会有颜色区别。
win.blur()
取消焦点。
win.isFocused()
判断窗口是否聚焦。
BrowserWindow.getFocusedWindow()
获取当前聚焦的窗口。这是 BrowserWindow 上的静态方法
当调用 win.show()来显示窗口时,一般也会触发聚焦。而 win.showInactive()是只显示窗口,不聚焦。当然 win.hide()隐藏窗口时也会触发失焦。
我们可以使用 api 对窗口的位置进行调整,如果用户拖动了窗口,也可以获取窗口的当前位置。
win.moveTop()
将窗口移动到最上面
win.center()
将窗口移动到屏幕中间
win.setPosition(x, y)
将窗口移动到坐标(x, y)
win.getPosition()
获取窗口的位置坐标
全屏和最大化
win.setFullScreen(flag)
设置窗口全屏或退出全屏,同时会触发进入全屏的事件: 'enter-full-screen',或退出全屏事件: 'leave-full-screen'。另外,如果全屏是由 html api 触发的,那么会触发事件: 'enter-html-full-screen'或事件: 'leave-html-full-screen'。
win.isFullScreen()
判断窗口当前是否全屏
win.maximize()
最大化窗口。注意最大化窗口和全屏不一样,最大化还是会包含工具栏的一些操作。
win.unmaximize()
取消最大化
win.isMaximized()
判断窗口是否处于最大化
win.minimize()
最小化窗口
win.isMinimized()
判断窗口是否处于最小化
win.restore()
将窗口恢复到之前的状态,比如窗口在非全屏的状态下,调用了最小化,这时需要恢复,可以使用此 api,窗口会恢复到最小化之前的位置和尺寸。
Electron 窗口根据展现形式的不同分为几种常用类型:无边框窗口、父子窗口、模态窗口和透明窗口等。我们分别介绍下。
无边框窗口
无边框窗口是没有默认顶部工具栏和边框的一种窗口,只有我们的内容区域
要创建无边框窗口只需要在窗口初始化时 frame: false 即可。这种类型比较适合我们的一些弹层场景。由于我们的拖动、关闭等功能默认都在顶部工具栏,所以无边框窗口将丢失这些能力。但我们可以在 html 里面为顶部增加自定义的工具栏,从而达到美化工具栏或增加功能的作用,这里就可以借助 win 的 resize、close、minimize 等 api 来实现自定义的工具栏。而拖动我们可以利用 css 的-webkit-app-region: drag 来设置哪个区域是可以拖动的。
在 mac 环境,还可以通过参数 titleBarStyle:hidden 或 titleBarStyle:hiddenInset 来达到类似效果,这种设置可以保留 mac 应用左上角的“红绿灯”,同时隐藏顶部默认工具栏,如下图。
通过 new BrowserWindow 创建的窗口时可以通过参数 parent 来为其指定父窗口
// main.js
const { BrowserWindow } = require('electron')
const parentWin = new BrowserWindow()
const childWin = new BrowserWindow({parent: parentWin})
上面 parentWin 将是 childWin 的父窗口,子窗口将始终显示在父窗口的顶部,并且随着父窗口的销毁而销毁。注意一些差异:在 mac 系统中拖动父窗口时,子窗口也会跟着同步移动,子窗口可以单独关闭和拖动。不过在 windows 系统中,拖动父窗口,子窗口不会跟着同步。
除了上面初始化时指定父窗口,还可以通过 win.setParentWindow(parent)来动态设置父窗口,一个父窗口可以有多个子窗口。当然可以通过 api 获取已经设置的父子窗口,参考下面的 api
// main.js
const { BrowserWindow } = require('electron')
const parentWin = new BrowserWindow()
const childWin = new BrowserWindow()
childWin.setParentWindow(parentWin) // 设置父窗口哦
childWin.getParentWindow() // 获取 childWin 的父窗口
parentWin.getChildWindows() // 获取 parentWin 的所有子窗口
模态窗口我们在 html 开发中经常用到,比如弹出一个用户输入或确认层,在关闭之前不能操作其他功能。Electron 中模态窗口是通过 modal 属性来设置的,由于模态窗口也是父子窗口,所以在初始化时需要设置 parent 和 modal 两个参数。模态窗口一般在父窗口上面比父窗口小一些,用户只能操作模态窗口里面的内容,在关闭模态窗口后,底下的父级窗口才会激活,此时才能操作或关闭父级窗口。
// main.js
const { BrowserWindow } = require('electron')
const parentWin = new BrowserWindow({})
const modalWin = new BrowserWindow({
parent: parentWin,
modal: true, // 表示为模态窗口
需要说明的是,在 windows 系统中 Electron 模态窗口的表现是和预期一致的,但是在 mac 系统中不太一样,子窗口缺少了顶部工具栏,导致无法关闭。所以如果在 mac 系统中使用,需要自己补充子窗口的关闭逻辑。由于模态窗口应用比较广泛,并且实现成本不高,所以实际项目中建议通过 html 来自定义实现模态窗口,而不是使用 Electron 提供的模态窗口。
还有一种比较小的应用场景-透明窗口,即整个背景都是透明的,只有 html 的内容。也就是视觉上它可以没有边框的同时,还能改变其形状,至少可以不是一个矩形,因为我们可以通过 css 来设置 html 的各种显示逻辑。想一想桌面上 360 安全软件的那个小球是不是就明白了?这种窗口可以使用 transparent 参数来设置。需要注意的是如果窗口调试窗打开了,在开发模式可能看到的不是透明窗口,这是正常的,调试窗关闭的情况下就能恢复正常,所以开发调试时不要陷进去以为设置无效。
// main.js
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
transparent: true,// 表示为透明窗口
frame: false, // 透明属性与 frame 互斥,所以最好同时设置 frame 为 false
webview 和 BrowserView
webview 的表现形式类似于 html 中的 iframe,是在渲染进程中使用的一个标签,用于独立加载某个页面,但不同的是 webview 和所在窗口是相互不同的进程,也就是处理它们的交互时和 iframe 不一样,webview 的交互形式和窗口之间差不多,即虽然视觉上看起来像是同一个页面,实际进程间相互独立。因此 webview 的交互其实是相对比较繁琐的,还包括一些其他不稳定性,Electron 不太建议使用此标签。
// render.html
<webview src="https://www.github.com/" style="display:inline-flex; width:640px; height:480px"></webview>
BrowserView 类似 webview 和 iframe,它可以用来代替 webview,类似子窗口,但是展现就像 iframe 一样嵌入在页面里面。稳定性和可靠性都比 webview 好一些。因为 webview 标签基于 Chromium webview,所以其架构可能会不断变化,具有不确定性。
// main.js
const { BrowserView, BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 600 })
const view = new BrowserView()
win.setBrowserView(view)
view.setBounds({ x: 0, y: 0, width: 300, height: 300 })
view.webContents.loadURL('https://electronjs.org')
上面已经了解了 BrowserWindow 实例的常用属性和实例方法,现在我们简单介绍下窗口上的一些常用静态 api,这些 api 对于我们设计主进程业务逻辑架构和进行窗口管理时有很大帮助。
BrowserWindow.getAllWindows()
获取所有已经打开的窗口,在用户需要“关闭所有窗口”或“关闭其他窗口”或“退出程序”等操作中适合使用
BrowserWindow.getFocusedWindow()
获取当前激活的窗口(聚焦的窗口)
BrowserWindow.fromId(id)
根据窗口 id 获取其实例,有些场景可能我们只存储了窗口的 id(win.id)
本讲主要介绍了最常用的一些窗口属性和 api,以及一切适用场景。窗口是 Electron 开发中非常重要的模块,实际开发中,很大一部分时间都是在和窗口打交道。熟练掌握窗口的各种特性和 api 将有助于我们更加顺畅地开发应用程序,合理设计和组织业务模块,更加理解桌面程序的运行机制。有了本讲的窗口基础,后面的章节我们将进一步介绍窗口间的通信和其他交互,到时我们对 Electron 窗口运用能力的熟练掌握也将水到渠成。
我们知道了 Electron 窗口关闭时会触发一些事件以便我们做一些业务逻辑处理,那么如果用户直接关闭电脑呢,这种是否还能进入我们的监听处理事件呢,比如自动帮助用户保存一些数据?windows 和 mac 上的表现又有啥区别吗?相信你能找到答案!