窗口管理
原创2026/9/9...大约 7 分钟
窗口管理 🪟
窗口是 Electron 应用的"门面",所有的 UI 交互都发生在窗口中。本章我们系统学习窗口的创建、配置、生命周期以及多窗口管理。
创建窗口 💎
通过 BrowserWindow 类可以创建一个应用窗口:
import { BrowserWindow } from 'electron'
import { join } from 'path'
const win = new BrowserWindow({
width: 1024,
height: 768,
// ... 其它配置
})常用配置项 💎
BrowserWindow 支持非常丰富的配置项,下面列出最常用的部分:
| 配置项 | 类型 | 说明 |
|---|---|---|
width | number | 窗口宽度 |
height | number | 窗口高度 |
x, y | number | 窗口位置 |
minWidth | number | 最小宽度 |
minHeight | number | 最小高度 |
maxWidth | number | 最大宽度 |
maxHeight | number | 最大高度 |
resizable | boolean | 是否允许调整大小(默认 true) |
movable | boolean | 是否允许移动 |
minimizable | boolean | 是否允许最小化 |
maximizable | boolean | 是否允许最大化 |
closable | boolean | 是否允许关闭 |
fullscreen | boolean | 是否全屏 |
title | string | 窗口标题 |
icon | string | 窗口图标 |
show | boolean | 创建后是否显示(默认 true) |
frame | boolean | 是否显示原生窗口边框(默认 true) |
transparent | boolean | 是否透明窗口(仅部分平台支持) |
alwaysOnTop | boolean | 是否始终在最顶层 |
backgroundColor | string | 窗口背景色(避免白屏闪烁) |
webPreferences | object | 渲染进程相关配置 |
parent | BrowserWindow | 父窗口(创建子窗口时使用) |
modal | boolean | 是否为模态窗口 |
webPreferences 子配置 💎
webPreferences 用于配置渲染进程,强烈建议 显式指定每一项:
const win = new BrowserWindow({
webPreferences: {
preload: join(__dirname, '../preload/index.js'), // 预加载脚本
contextIsolation: true, // 启用上下文隔离(推荐)
nodeIntegration: false, // 关闭 Node 集成(推荐)
sandbox: false, // 是否启用沙箱
webSecurity: true, // 是否启用 Web 安全(同源策略)
allowRunningInsecureContent: false,
experimentalFeatures: false,
webviewTag: false, // 是否启用 <webview> 标签
},
})安全最佳实践
- ✅ 始终开启
contextIsolation: true - ✅ 始终关闭
nodeIntegration: false - ❌ 避免启用
webSecurity: false,会让应用存在跨域风险
窗口事件 💎
每个窗口都有完整的事件生命周期:
const win = new BrowserWindow({ /* ... */ })
// 窗口创建后
win.on('ready-to-show', () => {
win.show() // 配合 show: false 避免白屏
})
// 窗口被关闭
win.on('close', (event) => {
// 可以阻止默认关闭行为
// event.preventDefault()
console.log('窗口即将关闭')
})
// 窗口已关闭
win.on('closed', () => {
console.log('窗口已销毁')
// 注意:closed 后 win 不能再使用
})
// 窗口尺寸变化
win.on('resize', () => {
console.log('窗口大小:', win.getSize())
})
// 窗口移动
win.on('move', () => {
console.log('窗口位置:', win.getPosition())
})
// 窗口最大化 / 最小化 / 还原
win.on('maximize', () => console.log('已最大化'))
win.on('minimize', () => console.log('已最小化'))
win.on('unmaximize', () => console.log('已还原'))避免白屏闪烁
创建窗口时设置 show: false,等 ready-to-show 事件触发后再显示窗口,可以避免加载过程中的白屏:
const win = new BrowserWindow({
show: false,
backgroundColor: '#1e1e1e',
})
win.on('ready-to-show', () => win.show())窗口方法 💎
// 显示 / 隐藏
win.show()
win.hide()
win.showInactive() // 显示但不抢焦点
// 最大化 / 最小化 / 还原
win.maximize()
win.minimize()
win.unmaximize()
win.restore()
// 全屏
win.setFullScreen(true)
win.isFullScreen()
// 关闭 / 销毁
win.close()
win.destroy()
// 尺寸与位置
win.getSize() // [width, height]
win.getPosition() // [x, y]
win.setSize(1200, 800)
win.setPosition(100, 100)
win.setBounds({ x: 0, y: 0, width: 1200, height: 800 })
// 居中
win.center()
// 焦点
win.focus()
win.blur()
win.isFocused()
// 标题与图标
win.setTitle('新标题')
win.setIcon(path.join(__dirname, '../resources/icon.png'))
// 置顶
win.setAlwaysOnTop(true)
win.isAlwaysOnTop()
// 开发者工具
win.webContents.openDevTools()
win.webContents.closeDevTools()
win.webContents.toggleDevTools()自定义无边框窗口 💎
通过 frame: false 可以创建无原生边框的窗口,常用于自定义标题栏:
const win = new BrowserWindow({
width: 1024,
height: 768,
frame: false, // 隐藏原生边框
titleBarStyle: 'hiddenInset', // macOS 沉浸式标题栏
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
},
})预加载脚本中暴露窗口控制 API:
// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('windowControl', {
minimize: () => ipcRenderer.send('window:minimize'),
maximize: () => ipcRenderer.send('window:maximize'),
close: () => ipcRenderer.send('window:close'),
})主进程响应控制事件:
// src/main/index.ts
import { ipcMain, BrowserWindow } from 'electron'
function getFocusedWindow() {
return BrowserWindow.getFocusedWindow()
}
ipcMain.on('window:minimize', () => {
getFocusedWindow()?.minimize()
})
ipcMain.on('window:maximize', () => {
const win = getFocusedWindow()
if (win?.isMaximized()) {
win.unmaximize()
} else {
win?.maximize()
}
})
ipcMain.on('window:close', () => {
getFocusedWindow()?.close()
})渲染进程自定义标题栏(Vue 示例):
<template>
<div class="custom-titlebar">
<div class="title">我的应用</div>
<div class="controls">
<button @click="windowControl.minimize()">—</button>
<button @click="windowControl.maximize()">▢</button>
<button @click="windowControl.close()">✕</button>
</div>
</div>
</template>
<script setup lang="ts">
declare global {
interface Window {
windowControl: {
minimize: () => void
maximize: () => void
close: () => void
}
}
}
const windowControl = window.windowControl
</script>
<style scoped lang="scss">
.custom-titlebar {
height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
background: #1e1e1e;
color: #fff;
-webkit-app-region: drag; /* 允许拖动 */
user-select: none;
.controls button {
-webkit-app-region: no-drag; /* 按钮不可拖动 */
width: 46px;
height: 32px;
background: transparent;
border: 0;
color: #fff;
cursor: pointer;
&:hover { background: #2c2c2c; }
}
}
</style>多窗口管理 💎
对于多窗口应用(如 IM、IDE),通常会封装一个 WindowManager:
// src/main/windows.ts
import { BrowserWindow, app } from 'electron'
import { join } from 'path'
interface WindowConfig {
name: string
width?: number
height?: number
url?: string
}
class WindowManager {
private windows: Map<string, BrowserWindow> = new Map()
create(config: WindowConfig) {
// 防止重复创建同名窗口
if (this.windows.has(config.name)) {
const exist = this.windows.get(config.name)!
if (!exist.isDestroyed()) {
exist.focus()
return exist
}
}
const win = new BrowserWindow({
width: config.width || 1024,
height: config.height || 768,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
},
})
if (config.url) {
win.loadURL(config.url)
} else {
win.loadFile(join(__dirname, '../renderer/index.html'))
}
// 窗口关闭时从 map 中移除
win.on('closed', () => {
this.windows.delete(config.name)
})
this.windows.set(config.name, win)
return win
}
get(name: string) {
return this.windows.get(name)
}
close(name: string) {
const win = this.windows.get(name)
if (win && !win.isDestroyed()) {
win.close()
}
}
closeAll() {
this.windows.forEach((win) => win.close())
}
broadcast(channel: string, ...args: any[]) {
this.windows.forEach((win) => {
if (!win.isDestroyed()) {
win.webContents.send(channel, ...args)
}
})
}
}
export default new WindowManager()// src/main/index.ts
import WindowManager from './windows'
app.whenReady().then(() => {
// 创建主窗口
WindowManager.create({ name: 'main', width: 1200, height: 800 })
// 监听 IPC:创建子窗口
ipcMain.handle('open-settings', () => {
WindowManager.create({ name: 'settings', width: 600, height: 400 })
})
// 广播消息
ipcMain.on('broadcast-message', (_, msg) => {
WindowManager.broadcast('global-message', msg)
})
})父子窗口(模态) 💎
通过 parent + modal 可以创建父子窗口,子窗口会始终位于父窗口之上:
const mainWin = new BrowserWindow({ width: 1024, height: 768 })
// 创建模态子窗口
const childWin = new BrowserWindow({
width: 400,
height: 300,
parent: mainWin, // 指定父窗口
modal: true, // 模态:父窗口无法操作
title: '子窗口',
})
childWin.loadFile('child.html')屏幕与显示器 💎
import { screen } from 'electron'
// 获取所有显示器
const displays = screen.getAllDisplays()
console.log('显示器列表:', displays)
// 获取主显示器
const primary = screen.getPrimaryDisplay()
console.log('主显示器分辨率:', primary.size.width, 'x', primary.size.height)
// 获取当前鼠标所在显示器
const cursorDisplay = screen.getDisplayNearestPoint(screen.getCursorScreenPoint())
// 获取窗口所在显示器
const windowDisplay = screen.getDisplayNearestPoint(win.getBounds())
// 监听显示器变化
screen.on('display-added', () => console.log('新增显示器'))
screen.on('display-removed', () => console.log('移除显示器'))
screen.on('display-metrics-changed', () => console.log('显示器参数变化'))跨平台差异 💎
不同操作系统的窗口行为略有差异,需要特别注意:
| 行为 | Windows | macOS | Linux |
|---|---|---|---|
frame: false | 完全无边框 | 仍保留红绿灯按钮 | 完全无边框 |
| 关闭最后一个窗口 | 应用退出 | 应用不退出 | 应用退出 |
setIcon | 必须 .ico | 建议 .icns | 建议 .png |
alwaysOnTop | 正常 | 正常 | 部分 WM 不支持 |
| 透明窗口 | 支持 | 支持 | 支持有限 |
跨窗口应用退出处理
macOS 上即使所有窗口都关闭,应用也不退出,必须显式调用 app.quit():
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})实战案例:记住窗口位置 💎
每次启动应用时,恢复上次关闭窗口时的位置和大小:
// src/main/index.ts
import { app, BrowserWindow, screen } from 'electron'
import { join } from 'path'
import Store from 'electron-store'
const store = new Store<{
windowBounds: { x?: number; y?: number; width: number; height: number }
}>({
defaults: {
windowBounds: { width: 1024, height: 768 },
},
})
const createWindow = () => {
// 读取上次保存的窗口位置
const savedBounds = store.get('windowBounds')
// 校验保存的位置是否还在某个显示器范围内(防止外接显示器拔掉后窗口跑到屏幕外)
const displays = screen.getAllDisplays()
const isVisible = displays.some((display) => {
const { x, y, width, height } = display.workArea
if (savedBounds.x === undefined || savedBounds.y === undefined) return false
return (
savedBounds.x >= x &&
savedBounds.y >= y &&
savedBounds.x < x + width &&
savedBounds.y < y + height
)
})
const win = new BrowserWindow({
...savedBounds,
x: isVisible ? savedBounds.x : undefined,
y: isVisible ? savedBounds.y : undefined,
show: false,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
},
})
win.on('ready-to-show', () => win.show())
// 监听窗口变化时保存位置
const saveBounds = () => {
if (win.isMinimized() || win.isMaximized() || win.isFullScreen()) return
store.set('windowBounds', win.getBounds())
}
win.on('resize', saveBounds)
win.on('move', saveBounds)
win.on('close', saveBounds)
}electron-store
上面的代码使用了 electron-store,它是 Electron 生态中最流行的本地存储库:
pnpm add electron-store小结
本章我们学习了 Electron 窗口管理的完整内容:
- 创建与配置:
BrowserWindow的常用配置项与webPreferences安全实践 - 窗口事件:ready-to-show、close、resize 等
- 窗口方法:尺寸、位置、最大化、最小化等
- 自定义无边框:通过
frame: false+ 自定义标题栏 - 多窗口管理:封装 WindowManager
- 父子窗口:模态子窗口
- 跨平台差异:Windows、macOS、Linux 的不同处理
下一章我们将学习 系统菜单与托盘。
至此,本章节的学习就到此结束了,如有疑惑,可对接技术客服进行相关咨询。