mcpField
E
electrobun-window-management

electrobun-window-management

github.com/rajavijayach开发1194 次安装36 周前更新
代码仓库

关于 electrobun-window-management

面向 Electrobun 应用开发者的窗口管理技能,介绍创建和协调多个窗口及嵌入式 BrowserView 的模式。内容还包括窗口生命周期、状态保存和窗口间通信示例。

主要功能

  1. 01多窗口管理
  2. 02嵌入 BrowserView 与标签页
  3. 03窗口状态持久化
  4. 04父子窗口组
  5. 05窗口间消息与广播
  6. 06浮动、模态和定位窗口

适用场景

  1. 01组织主窗口、设置窗口和编辑器窗口
  2. 02使用 BrowserView 构建标签页界面
  3. 03恢复已保存的窗口尺寸与位置
  4. 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