关于 electrobun-window-management
面向 Electrobun 应用开发者的窗口管理技能,介绍创建和协调多个窗口及嵌入式 BrowserView 的模式。内容还包括窗口生命周期、状态保存和窗口间通信示例。
主要功能
- 01多窗口管理
- 02嵌入 BrowserView 与标签页
- 03窗口状态持久化
- 04父子窗口组
- 05窗口间消息与广播
- 06浮动、模态和定位窗口
适用场景
- 01组织主窗口、设置窗口和编辑器窗口
- 02使用 BrowserView 构建标签页界面
- 03恢复已保存的窗口尺寸与位置
- 04协调多个窗口间的消息
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ rajavijayach/ electrobun-skills --skill electrobun-window-management安装后在 Claude Code 中输入/electrobun-window-management确认已加载。
详细介绍
Electrobun 窗口管理
在 Electrobun 应用中管理窗口和视图的高级模式。
多窗口应用
基本窗口管理器
import { BrowserWindow } from "electrobun/bun";
class WindowManager {
private windows = new Map<string, BrowserWindow>();
createWindow(id: string, options: any) {
const win = new BrowserWindow(options);
this.windows.set(id, win);
win.on("close", () => {
this.windows.delete(id);
});
return win;
}
getWindow(id: string) {
return this.windows.get(id);
}
getAllWindows() {
return Array.from(this.windows.values());
}
closeWindow(id: string) {
const win = this.windows.get(id);
if (win) {
win.close();
}
}
closeAllWindows() {
this.windows.forEach(win => win.close());
this.windows.clear();
}
}
const windowManager = new WindowManager();
窗口工厂模式
type WindowType = "main" | "settings" | "editor" | "preview";
interface WindowConfig {
type: WindowType;
url: string;
width: number;
height: number;
frame?: boolean;
parent?: BrowserWindow;
}
class WindowFactory {
private configs: Record<WindowType, Partial<WindowConfig>> = {
main: {
url: "views://main/index.html",
width: 1200,
height: 800,
frame: true,
},
settings: {
url: "views://settings/index.html",
width: 600,
height: 500,
frame: true,
},
editor: {
url: "views://editor/index.html",
width: 1000,
height: 700,
},
preview: {
url: "views://preview/index.html",
width: 800,
height: 600,
frame: false,
}
};
create(type: WindowType, overrides?: Partial<WindowConfig>) {
const config = { ...this.configs[type], ...overrides };
return new BrowserWindow(config);
}
}
const factory = new WindowFactory();
const mainWindow = factory.create("main");
const settingsWindow = factory.create("settings", { width: 700 });
BrowserView
BrowserView 可以在窗口中嵌入 WebView,而无需创建单独的窗口。
基本 BrowserView
import { BrowserWindow, BrowserView } from "electrobun/bun";
const win = new BrowserWindow({
title: "Browser App",
width: 1200,
height: 800,
});
const view = new BrowserView({
bounds: { x: 0, y: 60, width: 1200, height: 740 }
});
win.addBrowserView(view);
view.loadURL("https://example.com");
// Update bounds on window resize
win.on("resize", ({ width, height }) => {
view.setBounds({ x: 0, y: 60, width, height: height - 60 });
});
使用 BrowserView 构建标签页系统
class TabManager {
private tabs: Map<string, BrowserView> = new Map();
private activeTab: string | null = null;
private window: BrowserWindow;
constructor(window: BrowserWindow) {
this.window = window;
}
createTab(id: string, url: string) {
const view = new BrowserView({
bounds: this.getViewBounds()
});
view.loadURL(url);
this.tabs.set(id, view);
if (!this.activeTab) {
this.activateTab(id);
}
return view;
}
activateTab(id: string) {
const view = this.tabs.get(id);
if (!view) return;
// Hide current tab
if (this.activeTab) {
const currentView = this.tabs.get(this.activeTab);
if (currentView) {
this.window.removeBrowserView(currentView);
}
}
// Show new tab
this.window.addBrowserView(view);
this.activeTab = id;
}
closeTab(id: string) {
const view = this.tabs.get(id);
if (!view) return;
if (this.activeTab === id) {
this.window.removeBrowserView(view);
this.activeTab = null;
// Activate another tab
const otherTab = Array.from(this.tabs.keys()).find(k => k !== id);
if (otherTab) {
this.activateTab(otherTab);
}
}
this.tabs.delete(id);
}
private getViewBounds() {
const { width, height } = this.window.getBounds();
return { x: 0, y: 60, width, height: height - 60 };
}
updateBounds() {
const bounds = this.getViewBounds();
this.tabs.forEach(view => {
view.setBounds(bounds);
});
}
}
// Usage
const tabManager = new TabManager(mainWindow);
tabManager.createTab("tab1", "https://example.com");
tabManager.createTab("tab2", "https://github.com");
tabManager.activateTab("tab2");
mainWindow.on("resize", () => {
tabManager.updateBounds();
});
窗口生命周期管理
窗口状态持久化
import { paths } from "electrobun/bun";
import { join } from "path";
interface WindowState {
x: number;
y: number;
width: number;
height: number;
maximized: boolean;
}
class WindowStateManager {
private stateFile: string;
constructor(windowId: string) {
this.stateFile = join(paths.userData, `window-state-${windowId}.json`);
}
async save(state: WindowState) {
await Bun.write(this.stateFile, JSON.stringify(state, null, 2));
}
async load(): Promise<WindowState | null> {
try {
const content = await Bun.file(this.stateFile).text();
return JSON.parse(content);
} catch {
return null;
}
}
async restore(window: BrowserWindow) {
const state = await this.load();
if (!state) return false;
window.move({ x: state.x, y: state.y });
window.resize({ width: state.width, height: state.height });
if (state.maximized) {
window.maximize();
}
return true;
}
startTracking(window: BrowserWindow) {
let saveTimeout: Timer;
const saveState = () => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(async () => {
const bounds = window.getBounds();
await this.save({
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
maximized: window.isMaximized(),
});
}, 500);
};
window.on("move", saveState);
window.on("resize", saveState);
}
}
// Usage
const stateManager = new WindowStateManager("main");
const win = new BrowserWindow({ width: 1200, height: 800 });
await stateManager.restore(win);
stateManager.startTracking(win);
窗口组与父子关系
class WindowGroup {
private parent: BrowserWindow;
private children: BrowserWindow[] = [];
constructor(parent: BrowserWindow) {
this.parent = parent;
// Close children when parent closes
parent.on("close", () => {
this.closeAllChildren();
});
}
addChild(options: any) {
const child = new BrowserWindow({
...options,
parent: this.parent,
});
this.children.push(child);
child.on("close", () => {
const index = this.children.indexOf(child);
if (index > -1) {
this.children.splice(index, 1);
}
});
return child;
}
closeAllChildren() {
this.children.forEach(child => child.close());
this.children = [];
}
showAll() {
this.parent.show();
this.children.forEach(child => child.show());
}
hideAll() {
this.parent.hide();
this.children.forEach(child => child.hide());
}
}
// Usage
const mainWindow = new BrowserWindow({ width: 1200, height: 800 });
const group = new WindowGroup(mainWindow);
const palette = group.addChild({
url: "views://palette/index.html",
width: 250,
height: 600,
frame: false,
});
const inspector = group.addChild({
url: "views://inspector/index.html",
width: 300,
height: 400,
});
窗口编排
向所有窗口广播
class WindowBroadcaster {
private windows: Set<BrowserWindow> = new Set();
register(window: BrowserWindow) {
this.windows.add(window);
window.on("close", () => {
this.windows.delete(window);
});
}
async broadcast(method: string, ...args: any[]) {
const promises = Array.from(this.windows).map(win =>
win.rpc[method](...args).catch(err => {
console.error(`Broadcast to window failed:`, err);
})
);
await Promise.allSettled(promises);
}
}
const broadcaster = new WindowBroadcaster();
// Register windows
broadcaster.register(mainWindow);
broadcaster.register(settingsWindow);
// Broadcast to all
await broadcaster.broadcast("updateTheme", { theme: "dark" });
窗口通信中心
class WindowHub {
private windows = new Map<string, BrowserWindow>();
register(id: string, window: BrowserWindow) {
this.windows.set(id, window);
// Setup RPC handler for messaging
window.defineRpc({
handlers: {
sendToWindow: async (targetId: string, method: string, ...args: any[]) => {
return this.send(targetId, method, ...args);
}
}
});
}
async send(targetId: string, method: string, ...args: any[]) {
const target = this.windows.get(targetId);
if (!target) {
throw new Error(`Window ${targetId} not found`);
}
return await target.rpc[method](...args);
}
async broadcast(method: string, ...args: any[]) {
const results = new Map();
for (const [id, window] of this.windows) {
try {
const result = await window.rpc[method](...args);
results.set(id, result);
} catch (err) {
results.set(id, { error: err.message });
}
}
return results;
}
}
const hub = new WindowHub();
hub.register("main", mainWindow);
hub.register("editor", editorWindow);
// Send from main to editor
await hub.send("editor", "updateContent", { text: "Hello" });
// Broadcast to all
await hub.broadcast("refreshData");
浮动窗口与覆盖层
始终置顶的窗口
const floatingWindow = new BrowserWindow({
url: "views://floating/index.html",
width: 300,
height: 200,
frame: false,
alwaysOnTop: true,
skipTaskbar: true,
});
// Toggle always-on-top
floatingWindow.setAlwaysOnTop(!floatingWindow.isAlwaysOnTop());
画中画模式
class PictureInPicture {
private pipWindow: BrowserWindow | null = null;
private sourceWindow: BrowserWindow;
constructor(sourceWindow: BrowserWindow) {
this.sourceWindow = sourceWindow;
}
enter(url: string) {
if (this.pipWindow) return;
this.pipWindow = new BrowserWindow({
url,
width: 400,
height: 300,
frame: false,
alwaysOnTop: true,
resizable: true,
});
this.pipWindow.on("close", () => {
this.pipWindow = null;
});
}
exit() {
if (this.pipWindow) {
this.pipWindow.close();
this.pipWindow = null;
}
}
toggle(url: string) {
if (this.pipWindow) {
this.exit();
} else {
this.enter(url);
}
}
}
const pip = new PictureInPicture(mainWindow);
pip.enter("views://video-player/index.html");
高级模式
模态对话框
function showModal(parent: BrowserWindow, options: any) {
const modal = new BrowserWindow({
...options,
parent,
modal: true,
frame: false,
});
// Disable parent interaction
parent.setEnabled(false);
modal.on("close", () => {
parent.setEnabled(true);
parent.focus();
});
return modal;
}
// Usage
const confirmDialog = showModal(mainWindow, {
url: "views://confirm/index.html",
width: 400,
height: 200,
});
窗口定位
function centerWindow(window: BrowserWindow) {
const { screen } = require("electrobun/bun");
const display = screen.getPrimaryDisplay();
const { width: screenWidth, height: screenHeight } = display.workArea;
const { width, height } = window.getBounds();
const x = Math.floor((screenWidth - width) / 2);
const y = Math.floor((screenHeight - height) / 2);
window.move({ x, y });
}
function cascadeWindows(windows: BrowserWindow[]) {
const offset = 30;
windows.forEach((win, index) => {
win.move({
x: 100 + (index * offset),
y: 100 + (index * offset)
});
});
}
资源
了解更多 Electrobun 相关信息:
- 核心技能:
electrobun- 基本窗口创建和 API - RPC 模式:
electrobun-rpc-patterns- 窗口间 RPC - 原生 UI:
electrobun-native-ui- 菜单和托盘集成
评论
支持 Markdown