窗口管理
BrowserWindow 是 Electron 中创建和管理应用窗口的核心模块。本章将详细介绍窗口的创建、配置、事件处理以及多窗口管理。
创建窗口
基本创建
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
窗口配置选项
BrowserWindow 支持丰富的配置选项:
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
width: 1000,
height: 700,
minWidth: 400,
minHeight: 300,
maxWidth: 1920,
maxHeight: 1080,
x: 100,
y: 100,
center: true,
resizable: true,
movable: true,
minimizable: true,
maximizable: true,
closable: true,
focusable: true,
title: '我的应用',
icon: '/path/to/icon.png',
frame: true,
titleBarStyle: 'default',
backgroundColor: '#ffffff',
transparent: false,
opacity: 1.0,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
webSecurity: true,
javascript: true,
devTools: true
}
})
配置选项详解
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| width | number | 800 | 窗口宽度 |
| height | number | 600 | 窗口高度 |
| minWidth | number | 0 | 最小宽度 |
| minHeight | number | 0 | 最小高度 |
| maxWidth | number | 无限 | 最大宽度 |
| maxHeight | number | 无限 | 最大高度 |
| x | number | 居中 | 窗口 X 坐标 |
| y | number | 居中 | 窗口 Y 坐标 |
| center | boolean | false | 是否居中显示 |
| resizable | boolean | true | 是否可调整大小 |
| frame | boolean | true | 是否显示边框 |
| title | string | - | 窗口标题 |
| icon | string | - | 窗口图标路径 |
| backgroundColor | string | #FFF | 背景颜色 |
| transparent | boolean | false | 是否透明 |
加载内容
加载本地文件
win.loadFile('index.html')
win.loadFile('pages/about.html')
加载远程 URL
win.loadURL('https://example.com')
win.loadURL('http://localhost:3000')
加载 HTML 字符串
const html = `
<!DOCTYPE html>
<html>
<head>
<title>动态页面</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
`
win.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`)
窗口事件
常用事件
const win = new BrowserWindow()
win.on('close', (event) => {
event.preventDefault()
const choice = require('electron').dialog.showMessageBoxSync(win, {
type: 'question',
buttons: ['取消', '退出'],
title: '确认',
message: '确定要退出吗?'
})
if (choice === 1) {
win.destroy()
}
})
win.on('closed', () => {
win = null
})
win.on('focus', () => {
console.log('窗口获得焦点')
})
win.on('blur', () => {
console.log('窗口失去焦点')
})
win.on('maximize', () => {
console.log('窗口最大化')
})
win.on('unmaximize', () => {
console.log('取消最大化')
})
win.on('minimize', () => {
console.log('窗口最小化')
})
win.on('restore', () => {
console.log('窗口恢复')
})
win.on('resize', () => {
const [width, height] = win.getSize()
console.log(`窗口大小: ${width} x ${height}`)
})
win.on('move', () => {
const [x, y] = win.getPosition()
console.log(`窗口位置: (${x}, ${y})`)
})
事件列表
| 事件 | 说明 |
|---|---|
| close | 窗口即将关闭,可阻止 |
| closed | 窗口已关闭 |
| focus | 窗口获得焦点 |
| blur | 窗口失去焦点 |
| maximize | 窗口最大化 |
| unmaximize | 取消最大化 |
| minimize | 窗口最小化 |
| restore | 窗口恢复 |
| resize | 窗口大小改变 |
| move | 窗口位置改变 |
| ready-to-show | 窗口准备好显示 |
延迟显示
在页面加载完成后再显示窗口,避免闪烁:
const win = new BrowserWindow({
show: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.once('ready-to-show', () => {
win.show()
})
win.loadFile('index.html')
窗口控制方法
显示和隐藏
win.show()
win.hide()
win.isVisible()
win.isFocused()
最小化和恢复
win.minimize()
win.restore()
win.isMinimized()
最大化
win.maximize()
win.unmaximize()
win.isMaximized()
全屏
win.setFullScreen(true)
win.isFullScreen()
win.setSimpleFullScreen(true)
win.isSimpleFullScreen()
大小和位置
const [width, height] = win.getSize()
win.setSize(1024, 768)
const [x, y] = win.getPosition()
win.setPosition(100, 100)
win.setBounds({ x: 100, y: 100, width: 800, height: 600 })
const bounds = win.getBounds()
win.center()
win.setContentSize(800, 600)
const [contentWidth, contentHeight] = win.getContentSize()
窗口状态
win.focus()
win.blur()
win.setAlwaysOnTop(true, 'floating')
win.isAlwaysOnTop()
win.setSkipTaskbar(true)
win.setKiosk(true)
win.isKiosk()
无边框窗口
无边框窗口适合创建自定义标题栏的应用。
创建无边框窗口
const win = new BrowserWindow({
width: 800,
height: 600,
frame: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
macOS 标题栏样式
macOS 支持特殊的标题栏样式:
const win = new BrowserWindow({
titleBarStyle: 'hidden',
trafficLightPosition: { x: 10, y: 10 }
})
const win = new BrowserWindow({
titleBarStyle: 'hiddenInset'
})
const win = new BrowserWindow({
titleBarStyle: 'customButtonsOnHover',
frame: false
})
自定义标题栏实现
预加载脚本:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
minimizeWindow: () => ipcRenderer.send('window-minimize'),
maximizeWindow: () => ipcRenderer.send('window-maximize'),
closeWindow: () => ipcRenderer.send('window-close')
})
主进程:
const { BrowserWindow, ipcMain } = require('electron')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
frame: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}
ipcMain.on('window-minimize', () => mainWindow.minimize())
ipcMain.on('window-maximize', () => {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow.maximize()
}
})
ipcMain.on('window-close', () => mainWindow.close())
HTML/CSS:
<!DOCTYPE html>
<html>
<head>
<style>
.titlebar {
-webkit-app-region: drag;
height: 32px;
background: #1e1e1e;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
}
.titlebar-buttons {
-webkit-app-region: no-drag;
}
.titlebar-button {
width: 46px;
height: 32px;
border: none;
background: transparent;
color: #fff;
cursor: pointer;
}
.titlebar-button:hover {
background: rgba(255, 255, 255, 0.1);
}
.titlebar-button.close:hover {
background: #e81123;
}
</style>
</head>
<body>
<div class="titlebar">
<span>我的应用</span>
<div class="titlebar-buttons">
<button class="titlebar-button" onclick="minimize()">─</button>
<button class="titlebar-button" onclick="maximize()">□</button>
<button class="titlebar-button close" onclick="close()">✕</button>
</div>
</div>
<script>
function minimize() {
window.electronAPI.minimizeWindow()
}
function maximize() {
window.electronAPI.maximizeWindow()
}
function close() {
window.electronAPI.closeWindow()
}
</script>
</body>
</html>
父子窗口
创建模态窗口或关联窗口:
const { BrowserWindow } = require('electron')
const parent = new BrowserWindow({
width: 800,
height: 600
})
const child = new BrowserWindow({
width: 400,
height: 300,
parent: parent,
modal: true
})
child.loadFile('modal.html')
窗口关系
const parent = child.getParentWindow()
const children = parent.getChildWindows()
多窗口管理
窗口管理器
const { BrowserWindow } = require('electron')
class WindowManager {
constructor() {
this.windows = new Map()
}
create(id, options) {
const win = new BrowserWindow(options)
this.windows.set(id, win)
win.on('closed', () => {
this.windows.delete(id)
})
return win
}
get(id) {
return this.windows.get(id)
}
close(id) {
const win = this.windows.get(id)
if (win) {
win.close()
}
}
closeAll() {
this.windows.forEach(win => win.close())
}
getAll() {
return Array.from(this.windows.values())
}
}
const windowManager = new WindowManager()
windowManager.create('main', {
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
windowManager.create('settings', {
width: 500,
height: 400,
parent: windowManager.get('main'),
modal: true
})
单实例窗口
确保某个窗口只存在一个实例:
let settingsWindow = null
function openSettings() {
if (settingsWindow) {
settingsWindow.focus()
return
}
settingsWindow = new BrowserWindow({
width: 500,
height: 400
})
settingsWindow.on('closed', () => {
settingsWindow = null
})
settingsWindow.loadFile('settings.html')
}
窗口间通信
通过 IPC 通信
const mainWindow = new BrowserWindow()
const settingsWindow = new BrowserWindow()
mainWindow.webContents.send('settings-changed', settings)
settingsWindow.webContents.send('update-preview', data)
通过共享存储
const Store = require('electron-store')
const store = new Store()
store.set('theme', 'dark')
const theme = store.get('theme')
窗口快照
截图
const image = await win.webContents.capturePage()
const buffer = image.toPNG()
require('fs').writeFileSync('screenshot.png', buffer)
截取指定区域
const image = await win.webContents.capturePage({
x: 0,
y: 0,
width: 400,
height: 300
})
开发者工具
打开开发者工具
win.webContents.openDevTools()
win.webContents.openDevTools({ mode: 'detach' })
win.webContents.openDevTools({ mode: 'right' })
win.webContents.closeDevTools()
win.webContents.isDevToolsOpened()
开发者工具事件
win.webContents.on('devtools-opened', () => {
console.log('开发者工具已打开')
})
win.webContents.on('devtools-closed', () => {
console.log('开发者工具已关闭')
})
常见问题
窗口闪烁
使用 show: false 配合 ready-to-show 事件:
const win = new BrowserWindow({
show: false,
backgroundColor: '#fff'
})
win.once('ready-to-show', () => {
win.show()
})
内存泄漏
确保在窗口关闭时清理引用:
let win = new BrowserWindow()
win.on('closed', () => {
win = null
})
窗口白屏
检查文件路径是否正确,使用绝对路径:
const path = require('node:path')
win.loadFile(path.join(__dirname, 'index.html'))
小结
本章介绍了 Electron 窗口管理的核心内容:
- 窗口创建:配置选项、加载内容
- 窗口事件:close、focus、resize 等事件处理
- 窗口控制:显示、隐藏、最小化、最大化、全屏
- 无边框窗口:自定义标题栏实现
- 多窗口管理:窗口管理器、父子窗口
- 窗口间通信:IPC 和共享存储
掌握窗口管理是开发 Electron 应用的基础技能。