Loading...
Loading...
Compare original and translation side by side
Quick Guide: Usefor single-view windows. UseBrowserWindow+BaseWindowfor multi-view layouts (tabs, split panes, panels).WebContentsViewis deprecated since Electron 30 -- migrate toBrowserView. Track windows with aWebContentsViewregistry. Communicate between windows via the main process orMap<string, BrowserWindow>for direct renderer-to-renderer channels. Persist window bounds manually or use the upcomingMessagePortAPI. Always closewindowStatePersistenceexplicitly when usingwebContents-- unlikeBaseWindow, it does not auto-cleanup.BrowserWindow
快速指南: 单视图窗口使用。多视图布局(标签页、拆分窗格、面板)使用BrowserWindow+BaseWindow。WebContentsView自Electron 30起已被弃用——请迁移至BrowserView。使用WebContentsView注册表跟踪窗口。通过主进程或Map<string, BrowserWindow>实现窗口间通信,后者可建立直接的渲染器到渲染器通道。手动持久化窗口边界,或使用即将推出的MessagePortAPI。使用windowStatePersistence时必须显式关闭BaseWindow——与webContents不同,它不会自动清理资源。BrowserWindow
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
webContentsBaseWindowBrowserWindowWebContentsViewBrowserViewBrowserViewMessagePortBrowserWindow所有代码必须遵循CLAUDE.md中的项目规范(短横线命名、命名导出、导入顺序、、命名常量)import type
BaseWindowwebContentsBrowserWindowWebContentsViewBrowserViewBrowserViewMessagePortBrowserWindowconst { BaseWindow, WebContentsView } = require("electron");
const win = new BaseWindow({ width: 1200, height: 800 });
const sidebar = new WebContentsView({
webPreferences: { preload: path.join(__dirname, "preload.js") },
});
const main = new WebContentsView({
webPreferences: { preload: path.join(__dirname, "preload.js") },
});
win.contentView.addChildView(sidebar);
win.contentView.addChildView(main);
const SIDEBAR_WIDTH = 250;
sidebar.setBounds({ x: 0, y: 0, width: SIDEBAR_WIDTH, height: 800 });
main.setBounds({ x: SIDEBAR_WIDTH, y: 0, width: 950, height: 800 });
sidebar.webContents.loadFile("sidebar.html");
main.webContents.loadFile("main.html");webPreferenceswebContentsconst { BaseWindow, WebContentsView } = require("electron");
const win = new BaseWindow({ width: 1200, height: 800 });
const sidebar = new WebContentsView({
webPreferences: { preload: path.join(__dirname, "preload.js") },
});
const main = new WebContentsView({
webPreferences: { preload: path.join(__dirname, "preload.js") },
});
win.contentView.addChildView(sidebar);
win.contentView.addChildView(main);
const SIDEBAR_WIDTH = 250;
sidebar.setBounds({ x: 0, y: 0, width: SIDEBAR_WIDTH, height: 800 });
main.setBounds({ x: SIDEBAR_WIDTH, y: 0, width: 950, height: 800 });
sidebar.webContents.loadFile("sidebar.html");
main.webContents.loadFile("main.html");webPreferenceswebContents| Deprecated (BrowserView) | Replacement (WebContentsView) |
|---|---|
| |
| |
| |
| |
| |
| Manual resize via |
view.setBackgroundColor("#00000000")| 已弃用(BrowserView) | 替代方案(WebContentsView) |
|---|---|
| |
| |
| |
| |
| |
| 通过 |
view.setBackgroundColor("#00000000")ready-to-showcloseclosedconst win = new BrowserWindow({ show: false });
// Prevent white flash -- show only after first paint
win.once("ready-to-show", () => {
win.show();
});
// Intercept close for unsaved changes
win.on("close", (event) => {
if (hasUnsavedChanges()) {
event.preventDefault();
promptSaveDialog(win);
}
});
// Final cleanup after window is gone
win.on("closed", () => {
windowRegistry.delete(win.id);
});ready-to-showBrowserWindowwebContentsBaseWindowwebContentswebContentsview.webContents.once("ready-to-show", ...)ready-to-showcloseclosedconst win = new BrowserWindow({ show: false });
// 防止白屏闪烁——首次绘制完成后再显示
win.once("ready-to-show", () => {
win.show();
});
// 拦截关闭操作以处理未保存的更改
win.on("close", (event) => {
if (hasUnsavedChanges()) {
event.preventDefault();
promptSaveDialog(win);
}
});
// 窗口关闭后的最终清理
win.on("closed", () => {
windowRegistry.delete(win.id);
});ready-to-showBrowserWindowwebContentsBaseWindowwebContentswebContentsview.webContents.once("ready-to-show", ...)const parent = new BrowserWindow({ width: 800, height: 600 });
// Child window: always on top of parent, non-blocking
const child = new BrowserWindow({
parent,
width: 400,
height: 300,
});
// Modal window: blocks parent interaction
const modal = new BrowserWindow({
parent,
modal: true,
show: false,
width: 500,
height: 400,
});
modal.once("ready-to-show", () => modal.show());const parent = new BrowserWindow({ width: 800, height: 600 });
// 子窗口:始终位于父窗口上方,非阻塞
const child = new BrowserWindow({
parent,
width: 400,
height: 300,
});
// 模态窗口:阻塞父窗口交互
const modal = new BrowserWindow({
parent,
modal: true,
show: false,
width: 500,
height: 400,
});
modal.once("ready-to-show", () => modal.show());const windowRegistry = new Map();
function createWindow(id, options) {
const win = new BrowserWindow(options);
windowRegistry.set(id, win);
win.on("closed", () => {
windowRegistry.delete(id);
});
return win;
}
// Find and focus a window by ID
function focusWindow(id) {
const win = windowRegistry.get(id);
if (!win) return;
if (win.isMinimized()) win.restore();
win.focus();
}closedconst windowRegistry = new Map();
function createWindow(id, options) {
const win = new BrowserWindow(options);
windowRegistry.set(id, win);
win.on("closed", () => {
windowRegistry.delete(id);
});
return win;
}
// 通过ID查找并聚焦窗口
function focusWindow(id) {
const win = windowRegistry.get(id);
if (!win) return;
if (win.isMinimized()) win.restore();
win.focus();
}closed// Pattern A: Main process relay
ipcMain.on("message-to-window", (_event, targetId, channel, data) => {
const target = windowRegistry.get(targetId);
if (target) target.webContents.send(channel, data);
});
// Pattern B: MessagePort -- direct renderer-to-renderer
const { MessageChannelMain } = require("electron");
const { port1, port2 } = new MessageChannelMain();
window1.webContents.postMessage("port", null, [port1]);
window2.webContents.postMessage("port", null, [port2]);postMessagesend// 模式A:主进程转发
ipcMain.on("message-to-window", (_event, targetId, channel, data) => {
const target = windowRegistry.get(targetId);
if (target) target.webContents.send(channel, data);
});
// 模式B:MessagePort——直接渲染器到渲染器通信
const { MessageChannelMain } = require("electron");
const { port1, port2 } = new MessageChannelMain();
window1.webContents.postMessage("port", null, [port1]);
window2.webContents.postMessage("port", null, [port2]);postMessagesendfunction saveWindowState(win, stateFile) {
const bounds = win.getBounds();
const state = {
bounds,
isMaximized: win.isMaximized(),
isFullScreen: win.isFullScreen(),
displayId: screen.getDisplayMatching(bounds).id,
};
fs.writeFileSync(stateFile, JSON.stringify(state));
}function saveWindowState(win, stateFile) {
const bounds = win.getBounds();
const state = {
bounds,
isMaximized: win.isMaximized(),
isFullScreen: win.isFullScreen(),
displayId: screen.getDisplayMatching(bounds).id,
};
fs.writeFileSync(stateFile, JSON.stringify(state));
}screenconst { screen } = require("electron");
const displays = screen.getAllDisplays();
const externalDisplay = displays.find(
(d) => d.bounds.x !== 0 || d.bounds.y !== 0,
);
if (externalDisplay) {
const win = new BrowserWindow({
x: externalDisplay.bounds.x,
y: externalDisplay.bounds.y,
width: 800,
height: 600,
});
}workAreaboundsdisplay-addeddisplay-removeddisplay-metrics-changedscreenconst { screen } = require("electron");
const displays = screen.getAllDisplays();
const externalDisplay = displays.find(
(d) => d.bounds.x !== 0 || d.bounds.y !== 0,
);
if (externalDisplay) {
const win = new BrowserWindow({
x: externalDisplay.bounds.x,
y: externalDisplay.bounds.y,
width: 800,
height: 600,
});
}workAreaboundsdisplay-addeddisplay-removeddisplay-metrics-changedHow many web views does this window need?
+-- One full-size view?
| +-- BrowserWindow (simpler, automatic lifecycle)
+-- Multiple views (tabs, split pane, sidebar + content)?
| +-- BaseWindow + WebContentsView
+-- Frameless window with custom layout?
+-- One view? -> BrowserWindow with frame: false
+-- Multiple views? -> BaseWindow with frame: false该窗口需要多少个Web视图?
+-- 一个全屏视图?
| +-- BrowserWindow(更简单,自动管理生命周期)
+-- 多个视图(标签页、拆分窗格、侧边栏+内容)?
| +-- BaseWindow + WebContentsView
+-- 无边框窗口自定义布局?
+-- 一个视图? -> 带frame: false的BrowserWindow
+-- 多个视图? -> 带frame: false的BaseWindowHow should windows communicate?
+-- Simple, infrequent messages?
| +-- Main process relay (ipcMain/webContents.send)
+-- High-frequency or streaming data?
| +-- MessagePort (direct renderer-to-renderer after setup)
+-- Shared state across windows?
+-- Main process as single source of truth, push updates via IPC窗口应如何通信?
+-- 简单、低频消息?
| +-- 主进程转发(ipcMain/webContents.send)
+-- 高频或流式数据?
| +-- MessagePort(初始设置后直接渲染器到渲染器通信)
+-- 跨窗口共享状态?
+-- 主进程作为单一数据源,通过IPC推送更新What is the relationship between windows?
+-- Independent (editor, browser tabs)?
| +-- Separate BrowserWindow instances, window registry
+-- Always above parent (inspector, palette)?
| +-- Child window: { parent: parentWin }
+-- Blocks parent (save dialog, settings confirmation)?
+-- Modal window: { parent: parentWin, modal: true }窗口之间是什么关系?
+-- 独立窗口(编辑器、浏览器标签页)?
| +-- 独立的BrowserWindow实例,搭配窗口注册表
+-- 始终位于父窗口上方(检查器、面板)?
| +-- 子窗口:{ parent: parentWin }
+-- 阻塞父窗口(保存对话框、设置确认)?
+-- 模态窗口:{ parent: parentWin, modal: true }webContentsBaseWindowBrowserViewWebContentsViewBaseWindowBrowserWindowBrowserView.setAutoResize()WebContentsViewBrowserWindowclosedready-to-showBaseWindowBrowserWindowBaseWindowview.webContentsWebContentsView"#00000000"ipcRenderer.send()MessagePortpostMessage()display.boundsdisplay.workAreadisplay-removedaddChildView()setBounds()win.getBounds()WebContentsViewMessagePortMain.start()All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
webContentsBaseWindowBrowserWindowWebContentsViewBrowserViewBrowserViewMessagePort所有代码必须遵循CLAUDE.md中的项目规范(短横线命名、命名导出、导入顺序、、命名常量)import type
BaseWindowwebContentsBrowserWindowWebContentsViewBrowserViewBrowserViewMessagePort