electron
窗口管理与系统交互
By AI-Writer 11 min read
前言
Electron 的窗口管理能力远超普通浏览器窗口,支持自定义标题栏、透明窗口、全屏 kiosk 模式、多窗口层级关系、系统托盘和全局快捷键。本篇文章将全面讲解这些系统级交互功能,让你的桌面应用具备完整的原生体验。
BrowserWindow 高级配置
自定义标题栏
设置 frame: false 可以隐藏系统窗口边框,实现完全自定义的标题栏:
typescript
// main.ts
const win = new BrowserWindow({
width: 1200,
height: 800,
frame: false, // 隐藏原生标题栏
titleBarStyle: 'hidden',
titleBarOverlay: true, // Windows/Linux 上的窗口控件覆盖层
// macOS 上可添加 Traffic Light 位置
titleBarOverlay: {
color: '#ffffff',
symbolColor: '#333333',
height: 40,
},
});html
<!-- index.html — 自定义标题栏 -->
<header class="titlebar">
<div class="titlebar-drag">
<span class="title">我的 Electron 应用</span>
</div>
<div class="window-controls">
<button id="btn-minimize">−</button>
<button id="btn-maximize">□</button>
<button id="btn-close">✕</button>
</div>
</header>
<style>
.titlebar {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
background: #f5f5f5;
-webkit-app-region: drag; /* 可拖动区域 */
}
.window-controls { -webkit-app-region: no-drag; }
.window-controls button {
width: 46px;
height: 40px;
border: none;
background: transparent;
cursor: pointer;
}
</style>typescript
// index.html 中的渲染进程脚本
const { ipcRenderer } = require('electron');
document.getElementById('btn-minimize').onclick = () =>
ipcRenderer.send('window:minimize');
document.getElementById('btn-maximize').onclick = () =>
ipcRenderer.send('window:maximize');
document.getElementById('btn-close').onclick = () =>
ipcRenderer.send('window:close');typescript
// main.ts — IPC 处理器
ipcMain.on('window:minimize', (event) => {
BrowserWindow.fromWebContents(event.sender)?.minimize();
});
ipcMain.on('window:maximize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win?.isMaximized()) {
win.unmaximize();
} else {
win?.maximize();
}
});
ipcMain.on('window:close', (event) => {
BrowserWindow.fromWebContents(event.sender)?.close();
});注意:可拖动区域使用 -webkit-app-region: drag,内部按钮等控件需设置为 no-drag,否则无法点击。
透明窗口
typescript
// 透明窗口需要先设置 frame: false
const win = new BrowserWindow({
width: 400,
height: 400,
transparent: true, // 透明背景
frame: false,
resizable: false,
});
win.loadFile('circular-widget.html');css
/* circular-widget.html */
body {
background: transparent;
margin: 0;
}
.widget {
width: 300px;
height: 300px;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
color: white;
display: flex;
justify-content: center;
align-items: center;
}Kiosk 模式
Kiosk 模式让应用全屏运行并禁止退出,常用于展厅、ATM、自助终端:
typescript
const win = new BrowserWindow({
kiosk: true,
// kiosk: false 模式下可添加退出快捷键
});
// 退出 kiosk 模式
if (win.isKiosk()) {
win.setKiosk(false);
}多窗口管理
父子窗口
子窗口永远显示在父窗口之上,父窗口关闭时子窗口也会关闭:
typescript
function createMainWindow() {
const main = new BrowserWindow({ width: 1200, height: 800 });
main.loadFile('main.html');
return main;
}
function openSettings(parent: BrowserWindow) {
const settings = new BrowserWindow({
width: 500,
height: 400,
parent, // 关联父窗口
modal: true, // 模态窗口,父窗口被阻塞
show: false,
});
settings.loadFile('settings.html');
settings.once('ready-to-show', () => settings.show());
}窗口组
macOS 支持将多个窗口编组,关闭一个窗口时整组同时关闭:
typescript
import { app } from 'electron';
const win1 = new BrowserWindow();
const win2 = new BrowserWindow();
// macOS:将窗口加入同一 app 窗口组
app.addWindowsToAccessibilityGroup([win1, win2]);BrowserView
BrowserView 是一种轻量级的多窗口方案,将内容嵌入到主窗口的特定区域,而不是独立的 OS 窗口:
typescript
// main.ts — 使用 BrowserView
import { BrowserView, BrowserWindow } from 'electron';
const mainWindow = new BrowserWindow({ width: 1200, height: 800 });
const view = new BrowserView({
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
},
});
// 将 view 附加到主窗口
mainWindow.setBrowserView(view);
view.setBounds({ x: 0, y: 50, width: 1200, height: 750 });
view.webContents.loadURL('https://example.com');
// 主窗口标题栏区域由原生渲染
mainWindow.loadFile('index.html');系统托盘
创建托盘图标
typescript
// main.ts
import { Tray, Menu, nativeImage } from 'electron';
import path from 'path';
let tray: Tray;
function createTray() {
// 创建托盘图标
const iconPath = path.join(__dirname, '../resources/icon.png');
const icon = nativeImage.createFromPath(iconPath);
// macOS:建议使用 Template Image(会自动适配亮/暗模式)
const trayIcon = icon.resize({ width: 16, height: 16 });
tray = new Tray(trayIcon);
tray.setToolTip('My Electron App');
// 右键菜单
const contextMenu = Menu.buildFromTemplate([
{
label: '显示窗口',
click: () => {
const win = BrowserWindow.getAllWindows()[0];
win?.show();
win?.focus();
},
},
{ type: 'separator' },
{
label: '设置',
accelerator: 'CmdOrCtrl+,',
click: () => openSettings(),
},
{ type: 'separator' },
{
label: '退出',
accelerator: 'CmdOrCtrl+Q',
click: () => app.quit(),
},
]);
tray.setContextMenu(contextMenu);
// 点击托盘图标
tray.on('click', () => {
const win = BrowserWindow.getAllWindows()[0];
win?.show();
win?.focus();
});
// macOS:左键点击事件
tray.on('click', () => tray.popUpContextMenu());
}托盘菜单动态更新
typescript
// 动态更新托盘菜单
function updateTrayMenu(count: number) {
const newMenu = Menu.buildFromTemplate([
{
label: `未读消息: ${count}`,
enabled: false,
},
{ type: 'separator' },
{
label: '打开应用',
click: () => mainWindow.show(),
},
{ label: '退出', click: () => app.quit() },
]);
tray.setContextMenu(newMenu);
}全局快捷键
globalShortcut 在应用未聚焦时也能响应,适合音乐播放器(全局播放暂停)、截图工具等场景:
typescript
// main.ts
import { globalShortcut } from 'electron';
app.whenReady().then(() => {
// 注册全局快捷键
globalShortcut.register('MediaPlayPause', () => {
mainWindow.webContents.send('media:play-pause');
});
globalShortcut.register('CmdOrCtrl+Shift+S', () => {
mainWindow.webContents.send('app:screenshot');
});
// 检查快捷键是否注册成功
const isRegistered = globalShortcut.isRegistered('MediaPlayPause');
console.log('MediaPlayPause 已注册:', isRegistered);
});
app.on('will-quit', () => {
// 应用退出前注销所有全局快捷键
globalShortcut.unregisterAll();
});在渲染进程中使用
typescript
// preload.ts
contextBridge.exposeInMainWorld('electronAPI', {
onMediaPlayPause: (cb: () => void) => {
ipcRenderer.on('media:play-pause', cb);
return () => ipcRenderer.removeListener('media:play-pause', cb);
},
onScreenshot: (cb: () => void) => {
ipcRenderer.on('app:screenshot', cb);
return () => ipcRenderer.removeListener('app:screenshot', cb);
},
});屏幕信息获取
typescript
import { screen } from 'electron';
// 获取所有屏幕
const displays = screen.getAllDisplays();
console.log('屏幕数量:', displays.length);
// 获取当前鼠标所在屏幕
const currentDisplay = screen.getDisplayNearestPoint(
screen.getCursorScreenPoint()
);
console.log('当前屏幕分辨率:', currentDisplay.size);
// 获取主屏幕的工作区域(排除系统任务栏)
const workArea = screen.getPrimaryDisplay().workArea;
console.log('可用区域:', workArea);窗口状态持久化
应用关闭时保存窗口位置和大小,下次启动时恢复:
typescript
import { app } from 'electron';
import fs from 'fs';
import path from 'path';
const configPath = path.join(app.getPath('userData'), 'window-state.json');
interface WindowState {
x?: number;
y?: number;
width: number;
height: number;
isMaximized: boolean;
}
function loadWindowState(): WindowState {
try {
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
} catch {
return { width: 1200, height: 800, isMaximized: false };
}
}
function saveWindowState(win: BrowserWindow) {
const bounds = win.getBounds();
const state: WindowState = {
...bounds,
isMaximized: win.isMaximized(),
};
fs.writeFileSync(configPath, JSON.stringify(state));
}
app.whenReady().then(() => {
const savedState = loadWindowState();
const win = new BrowserWindow({
...savedState,
webPreferences: { nodeIntegration: false, contextIsolation: true },
});
if (savedState.isMaximized) win.maximize();
win.on('close', () => saveWindowState(win));
});小结
frame: false隐藏系统标题栏,配合-webkit-app-region实现自定义拖动区域transparent: true创建透明窗口,适合桌面小部件BrowserView是轻量级多区域内容嵌入方案- 系统托盘通过
Tray+Menu实现后台常驻和快捷操作 globalShortcut注册全局快捷键,即使应用未聚焦也能响应- 窗口状态持久化提升用户体验,下次启动恢复上次的窗口位置和大小
下一篇文章我们将介绍 原生对话框与文件操作,包括文件选择器、文件夹操作和 Shell 模块的使用。
#electron
#BrowserWindow
#Tray
#globalShortcut
#多窗口
评论
A
Written by
AI-Writer
Related Articles
electron
#3 IPC 通信机制详解
深入理解 Electron 的 IPC 模块:invoke/handle 双向异步模式、ipcRenderer.on 事件监听、channel 命名规范、进程间数据传输规则
Read More electron
#1 Electron 入门与核心架构
了解 Electron 的双引擎架构(Chromium + Node.js)、主进程与渲染进程的职责分工,快速搭建第一个 Hello World 应用,掌握 BrowserWindow 基本配置
Read More