窗口
主窗口隐藏和恢复
主窗口
为什么需要 主窗口?
一个应用存在着许多的窗口,需要一个窗口作为 __主窗口__,如果该窗口关闭,则意味着整个应用被关闭。
场景:在应用只有一个页面的时,用户点击关闭按钮,不想让整个应用关闭,而是隐藏;
例如:其他的app,像微信,QQ等桌面端。
主进程代码示例,完整代码在本应用的app/browser-window/index.ts
处
1 | let mainWindowId: number |
恢复主窗口显示
1 | const mainWindow = BrowserWindowsMap.get(mainWindowId) |
强制关闭主窗口
1 | const mainWindow = BrowserWindowsMap.get(mainWindowId) |
存在的问题
因为阻止了close事件,导致 关机 时无法关闭 __主窗口__,可以使用如下代码:
1 | app.on('before-quit', () => { |
注:macOS
Linux
Windows
完整文档
为避免启动 __多个应用__;
1 | app.on('second-instance', () => { |
注:macOS
Linux
Windows
完整文档
首次启动应用程序、尝试在应用程序已运行时或单击 应用程序 的 坞站 或 任务栏图标 时重新激活它;
1 | app.on('activate', () => { |
注:macOS
文档
双击托盘图标 打开app
,完整代码见app/tray
。
1 | tray.on('double-click', () => { |
注:macOS
Windows
文档
实例
你可以操作本应用的主窗口
文档
如果你还是新手,或者没有开发过electron
应用,浏览器打开 打造你的第一个 Electron 应用
全屏、最大化、最小化、关闭
全屏
创建时进入全屏
配置
new BrowserWindow({ fullscreen:true })
1 | // @@code-renderer: runner |
使用API
进入全屏
确保当前窗口的
fullscreenable:true
,以下API
才能使用
win.setFullScreen(flag)
,设置全屏状态;win.setSimpleFullScreen(flag)
,macOS
下独有,设置简单全屏。
全屏状态的获取
win.fullScreen
,来判断当前窗口是否全屏;win.isFullScreen()
,macOS
独有;win.isSimpleFullScreen()
,macOS
独有。
全屏事件的监听
rezise
调整窗口大小后触发;enter-full-screen
窗口进入全屏状态时触发;leave-full-screen
窗口离开全屏状态时触发;enter-html-full-screen
窗口进入由HTML API 触发的全屏状态时触发;leave-html-full-screen
窗口离开由HTML API触发的全屏状态时触发。
HTML
API
无法和窗口联动
试一试
1 | // @@code-renderer: runner |
使用按钮全屏和退出全屏是可以的,但是先点击左上角🚥全屏,再使用按钮退出全屏,是不行的。因为无法知道当前的状态是全屏,还是不是全屏。
解决办法:,将win.setFullScreen(flag)
方法挂载到窗口的window
上
最大化、最小化
创建窗口配置
1 | // @@code-renderer: runner |
当使用 minWidth/maxWidth/minHeight/maxHeight
设置最小或最大窗口大小时, 它只限制用户。 它不会阻止您将不符合大小限制的值传递给 setBounds/setSize
或 BrowserWindow
的构造函数。
相关事件
事件名称 | 触发条件 |
---|---|
maximize |
窗口最大化时触发 |
unmaximize |
当窗口从最大化状态退出时触发 |
minimize |
窗口最小化时触发 |
restore |
当窗口从最小化状态恢复时触发 |
相关状态API
win.minimizable
窗口是否可以最小化win.maximizable
窗口是否可以最大化win.isMaximized()
是否最大化win.isMinimized()
是否最小化
控制API
win.maximize()
使窗口最大化win.unmaximize()
退出最大化win.minimize()
使窗口最小化win.unminimize()
退出最小化
窗口恢复
win.restore()
将窗口从最小化状态恢复到以前的状态。
创建和管理窗口
通过electron
的BrowserWindow
模块,我们可以轻松 创建 和 管理 窗口。
在浏览器打开 完整的API文档
每个应用,因为业务的不同;创建和管理窗口的方式和流程也大不相同;这里我们以下流程为例:
创建窗口
通过BrowserWindow
,来 创建 或者 管理 新的浏览器窗口,每个浏览器窗口都有一个进程来管理。
这里,我们把 创建 和 管理__,分为 __用户行为 和 __应用本身__。
应用本身 即是:应用启动 ——> 窗口创建 –> 窗口展示
用户行为 即是:窗口已经创建完毕,用户点击a
链接而创建的窗口的行为
简单创建一个窗口
1 | // @@code-renderer: runner |
优化
__问题__:electron
的BrowserWindow
模块在创建时,如果没有配置show:false
,在创建之时就会显示出来,且默认的背景是白色;然后窗口请求HTML
,会出现闪烁。
解决
1 | // @@code-renderer: runner |
__对比上面简单创建窗口__,可以明显看出2者的区别
new BrowserWindow(options)
,options配置文档
管理窗口
管理应用创建的窗口
BrowserWindow
模块在创建窗口时,会返回 窗口实例
在这里使用Map
对象来存储这些 窗口实例
1 | const BrowserWindowsMap = new Map<number, BrowserWindow>() |
代码中提到 __主窗口__,什么是主窗口?主窗口隐藏和恢复章节
__窗口被关闭__,得把Map
中的实例删除。
1 | browserWindow.on('closed', () => { |
管理用户创建的窗口
一个窗口中存在许多的链接,不管是链接跳转,或是创建新的窗口,我们都需要管理这些窗口。
使用new-window
监听窗口创建
使用new-window
可监听 新窗口 的创建。浏览器打开完整文档
核心代码如下:
1 | // 创建窗口监听 |
注:关于
disposition
字段的解释,移步electron文档、electron源码、chrome 源码
扩展new-window
- 可以被 当前窗口的
new-window
事件捕捉到的
1 | window.open('https://github.com') |
1 | <a href='https://github.com' target='__blank'>链接</a> |
- 不可被 当前窗口的
new-window
事件捕捉到的
以下api需要窗口 集成node,即 主进程 创建时需配置渲染进程 使用1
2const { BrowserWindow } = require('electron')
const parent = new BrowserWindow({ webPreferences:{nodeIntegration: true}});BrowserWindow
创建窗口应用1
2
3
4const { BrowserWindow } = require('electron').remote
const win = new BrowserWindow()
win.loadURL('https://github.com')new-window
通过 __默认浏览器 来打开 __第三方链接1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import { shell } from 'electron'
function openExternal(url: string) {
const HTTP_REGEXP = /^https?:\/\//
// 非http协议不打开,防止出现自定义协议等导致的安全问题
if (!HTTP_REGEXP) {
return false
}
try {
await shell.openExternal(url, options)
return true
} catch (error) {
console.error('open external error: ', error)
return false
}
}
// 创建窗口监听
browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
if (disposition === 'foreground-tab') {
// 阻止鼠标点击链接
event.preventDefault()
openExternal(url)
}
})
关闭窗口
关闭窗口或者隐藏的API
win.close()
- 关闭页面,如果阻止
close
事件,将不会关闭页面,这会 __阻止计算机关闭__; - 关闭页面的服务,如
websocket
,下次打开窗口,窗口中的页面会 __重新渲染__; - 通过这个
API
触发的close
事件在unload
和beforeunload
之前触发,通过这点可以实现 __关闭时触发弹窗__; - 会被
closed
事件捕捉到。
__例子__:实现关闭窗口之前触发弹窗
代码如下:
1 | // @@code-renderer: runner |
注:
- 上面代码,只能应用于electron端,在web端刷新不起作用;web端无法在页面关闭或者刷新之前阻塞浏览器;
- 阻止关闭窗口出现的弹窗,
img
元素外链url
不起作用,会发出请求,但是不会渲染出来,可以使用div+background
的方式; - 关于
beforeunload
完整API
文档
win.destroy()
- 强制退出,无视
close
事件; - 关闭页面,以及页面内的服务,下次打开窗口,窗口中的页面会重新渲染;
- 会被
closed
事件捕捉到。
win.hide()
这个隐藏窗口。
- 隐藏窗口,会触发
hide
和blur
事件,同样也是可以通过event.preventDefault()
来阻止 - 只是隐藏窗口,通过
win.show()
,可以将窗口显现,并且会保持原来的窗口,里面的服务也不会挂断
其他
- 关于页面可见性,可参见文档;
- 主窗口的隐藏和唤醒 的具体细节,可参见本章的 主窗口隐藏和恢复章节;
- 窗口在被创建之后,窗口实例各种 事件触发顺序 可参见 窗口触发顺序章节;
- __窗口通信__,可参见 窗口通信章节;
- __无边框窗口__、__父子窗口__、__模态窗口__,参见窗口类型章节;
窗口之间的通信
主进程干预方式
主进程是可以干预渲染进程生成新的窗口的,只需要在创建窗口时,webContents
监听 new-window
1 | import path from 'path' |
在preload.js文件window.process.argv
,便能拿到父窗口的id,window.process.argv
是一个字符串数组,可以使用yargs来解析
preload.js 代码
1 | import { argv } from 'yargs' |
试一试
1 | // @@code-renderer: runner |
其余代码如下:
主窗口代码
1 | import React, { ReactElement, useEffect } from 'react' |
子窗口代码
1 | import React, { ReactElement, useEffect, useState } from 'react' |
父子窗口通信
和主进程干预,通过ipc
通信方式差不多,只是利用父子窗口这点,不用通过additionalArguments
传递父窗口id
,在子窗口通过window.parent
,就可以拿到父窗口
1 | browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => { |
弊端:子窗口永远在父窗口之上。
1 | // @@code-renderer: runner |
其余代码如下:
主窗口代码
1 | import React, { ReactElement, useEffect } from 'react' |
子窗口代码
1 | import React, { ReactElement, useEffect, useState } from 'react' |
使用window.open
窗口事件
窗口加载时
从上到下,依次执行
环境 | 事件 | 触发时机 |
---|---|---|
webPreferences的preload | - | 在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成Node, 此脚本都可以访问所有Node API 脚本路径为文件的绝对路径。 |
webContents | did-start-loading |
当tab中的旋转指针(spinner)开始旋转时,就会触发该事件 |
webContents | did-start-navigation |
当窗口开始导航是,触发该事件 |
窗口中的JavaScript |
DOMContentLoaded |
初始的 HTML 文档被完全加载和解析完成 |
窗口中的JavaScript |
load |
页面资源全部加载完成之时 |
BrowserWindow 实例 |
show |
窗口显示时触发时 |
webContents |
did-frame-navigate |
frame 导航结束时时 |
webContents |
did-navigate |
main frame 导航结束时时 |
BrowserWindow 实例 |
page-title-updated |
文档更改标题时触发 |
webContents |
page-title-updated |
文档更改标题时触发 |
webContents |
dom-ready |
一个框架中的文本加载完成后触发该事件 |
webContents |
did-frame-finish-load |
当框架完成导航(navigation)时触发 |
webContents |
did-finish-load |
导航完成时触发,即选项卡的旋转器将停止旋转 |
webContents |
did-stop-loading |
当tab中的旋转指针(spinner)结束旋转时,就会触发该事件 |
##. 窗口加载完毕,用户触发事件(不包括resize和move)
事件 | 作用 |
---|---|
page-title-updated |
文档更改标题时触发 |
blur |
当窗口失去焦点时触发 |
focus |
当窗口获得焦点时触发 |
hide |
窗口隐藏 |
show |
窗口显示 |
maximize |
窗口最大化时触发(mac是双击title) |
unmaximize |
当窗口从最大化状态退出时触发 |
enter-full-screen |
窗口进入全屏状态时触发 |
leave-full-screen |
窗口离开全屏状态时触发 |
enter-html-full-screen |
窗口进入由HTML API 触发的全屏状态时触发 |
leave-html-full-screen |
窗口离开由HTML API触发的全屏状态时触发 |
always-on-top-changed |
设置或取消设置窗口总是在其他窗口的顶部显示时触发。 |
app-command |
window linux 独有 |
用户移动窗口
- 移动窗口之前
will-move
; - 移动窗口中
move
; - 移动之后
moved
;
用户改变窗口大小
- 改变之前
will-resize
; - 改变之后
resize
窗口的内容异常事件(webContent
事件)
事件名 | 错误类型 |
---|---|
unresponsive |
网页变得未响应时触发 |
responsive |
未响应的页面变成响应时触发 |
did-fail-load |
加载失败,错误码 |
did-fail-provisional-load |
页面加载过程中,执行了window.stop() |
did-frame-finish-load |
|
crashed |
渲染进程崩溃或被结束时触发 |
render-process-gone |
渲染进程意外失败时发出 |
plugin-crashed |
有插件进程崩溃时触发 |
certificate-error |
证书的链接验证失败 |
preload-error |
preload.js 抛出错误 |
窗口关闭(包括意外关闭)
按触发顺序依次:(带window的是渲染进程中的)
- 关闭之前:close
- window.onbeforeunload
- window.onunload
- 关闭之后:closed
文档
窗口的聚焦和失焦
聚焦
创建窗口时配置:
1 | // @@code-renderer: runner |
focusable:true
窗口便可聚焦,便可以使用聚焦的api
focusable:false
在 Windows
中设置 focusable: false
也意味着设置了skipTaskbar: true
. 在 Linux
中设置 focusable: false
时窗口停止与 wm
交互, 并且窗口将始终置顶。
以下讨论的情况仅为focusable:true
情况下
1 | const { BrowserWindow } = require('electron'); |
关于聚焦的api
api | 功能 |
---|---|
BrowserWindow.getFocusedWindow() |
来获取聚焦的窗口 |
win.isFocused() |
判断窗口是否聚焦 |
win.on('focus',cb) |
来监听窗口是否聚焦 |
win.focus() |
手动聚焦窗口 |
其他api
副作用和聚焦有关的:
api | 功能 |
---|---|
win.show() |
显示窗口,并且聚焦于窗口 |
win.showInactive() |
显示窗口,但是不会聚焦于窗口 |
失焦
关于失焦的api
api | 功能 |
---|---|
win.blur() |
取消窗口聚焦 |
win.on('blur',cb) |
监听失焦 |
其他api
副作用和失焦有关的:
api | 功能 |
---|---|
win.hide() |
隐藏窗口,并且会触发失焦事件 |
窗口类型
无边框窗口
描述:
无边框窗口是不带外壳(包括窗口边框、工具栏等),只含有网页内容的窗口
实现
Windows
macOS
Linux
1 | // @@code-renderer: runner |
在macOS
下,还有不同的实现方式,完整文档
macOS
下独有的无边框
- 配置
titleBarStyle: 'hidden'
返回一个隐藏标题栏的全尺寸内容窗口,在左上角仍然有标准的窗口控制按钮(俗称“红绿灯”)
1 | // @@code-renderer: runner |
效果如下:
- 配置
titleBarStyle: 'hiddenInset'
返回一个另一种隐藏了标题栏的窗口,其中控制按钮到窗口边框的距离更大。
1 | // @@code-renderer: runner |
效果如下:
1.1.0.3.3. 配置titleBarStyle: 'customButtonsOnHover'
效果如下:
无边框窗口常见的问题
- 窗口顶部无法拖拽的问题
默认情况下, 无边框窗口是不可拖拽的。 应用程序需要在CSS
中指定-webkit-app-region: drag
来告诉Electron
哪些区域是可拖拽的(如操作系统的标准标题栏),在可拖拽区域内部使用-webkit-app-region: no-drag
则可以将其中部分区域排除。 请注意, 当前只支持矩形形状。完整文档
使用-webkit-app-region: drag
来实现拖拽,但是会导致内部的click
事件失效。这个时候可以将需要click
元素设置为-webkit-app-region: no-drag
。具体的细节可看issues
为了不影响窗口,这里拖拽的代码,应该在preload
触发。
核心代码:
1 | // 在顶部插入一个可以移动的dom |
试一试
1 | // @@code-renderer: runner |
父子窗口
子窗口始终在父窗口之上,在 窗口之间通信 章节中介绍到父子窗口之间的通信,窗口之间通信章节
1 | // @@code-renderer: runner |