跳到主要内容

窗口管理

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
}
})

配置选项详解

选项类型默认值说明
widthnumber800窗口宽度
heightnumber600窗口高度
minWidthnumber0最小宽度
minHeightnumber0最小高度
maxWidthnumber无限最大宽度
maxHeightnumber无限最大高度
xnumber居中窗口 X 坐标
ynumber居中窗口 Y 坐标
centerbooleanfalse是否居中显示
resizablebooleantrue是否可调整大小
framebooleantrue是否显示边框
titlestring-窗口标题
iconstring-窗口图标路径
backgroundColorstring#FFF背景颜色
transparentbooleanfalse是否透明

加载内容

加载本地文件

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 窗口管理的核心内容:

  1. 窗口创建:配置选项、加载内容
  2. 窗口事件:close、focus、resize 等事件处理
  3. 窗口控制:显示、隐藏、最小化、最大化、全屏
  4. 无边框窗口:自定义标题栏实现
  5. 多窗口管理:窗口管理器、父子窗口
  6. 窗口间通信:IPC 和共享存储

掌握窗口管理是开发 Electron 应用的基础技能。