窗口管理 #

一、窗口管理概述 #

1.1 多窗口架构 #

text
┌─────────────────────────────────────────────────────────────┐
│                         主进程                               │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                   窗口管理器                          │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐       │
│  │ 主窗口   │  │ 设置窗口 │  │ 关于窗口 │  │ 弹出窗口 │       │
│  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘       │
│       │            │            │            │             │
└───────┼────────────┼────────────┼────────────┼─────────────┘
        │            │            │            │
        ▼            ▼            ▼            ▼
   ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
   │渲染进程1│  │渲染进程2│  │渲染进程3│  │渲染进程4│
   └─────────┘  └─────────┘  └─────────┘  └─────────┘

1.2 窗口类型 #

类型 说明 示例
主窗口 应用主界面 主界面、仪表盘
设置窗口 配置选项 设置、偏好设置
关于窗口 应用信息 关于、版本信息
模态窗口 必须处理 确认对话框
工具窗口 辅助功能 开发者工具、日志窗口

二、窗口管理器实现 #

2.1 基础窗口管理器 #

javascript
// electron/main/windowManager.js
const { BrowserWindow } = require('electron');
const path = require('path');

class WindowManager {
    constructor() {
        this.windows = new Map();
    }

    create(name, options) {
        if (this.windows.has(name)) {
            const win = this.windows.get(name);
            win.focus();
            return win;
        }

        const win = new BrowserWindow({
            ...options,
            webPreferences: {
                preload: path.join(__dirname, '../preload/index.js'),
                nodeIntegration: false,
                contextIsolation: true,
                ...options.webPreferences
            }
        });

        this.windows.set(name, win);

        win.on('closed', () => {
            this.windows.delete(name);
        });

        return win;
    }

    get(name) {
        return this.windows.get(name);
    }

    has(name) {
        return this.windows.has(name);
    }

    close(name) {
        const win = this.windows.get(name);
        if (win) {
            win.close();
        }
    }

    hide(name) {
        const win = this.windows.get(name);
        if (win) {
            win.hide();
        }
    }

    show(name) {
        const win = this.windows.get(name);
        if (win) {
            win.show();
        }
    }

    closeAll() {
        for (const win of this.windows.values()) {
            win.close();
        }
    }

    getAll() {
        return Array.from(this.windows.entries());
    }

    broadcast(channel, data) {
        for (const win of this.windows.values()) {
            if (!win.isDestroyed()) {
                win.webContents.send(channel, data);
            }
        }
    }
}

module.exports = new WindowManager();

2.2 使用窗口管理器 #

javascript
// main.js
const windowManager = require('./windowManager');

// 创建主窗口
const mainWindow = windowManager.create('main', {
    width: 1024,
    height: 768,
    title: '我的应用'
});
mainWindow.loadFile('index.html');

// 创建设置窗口(按需创建)
ipcMain.handle('open-settings', () => {
    const settingsWin = windowManager.create('settings', {
        width: 600,
        height: 500,
        title: '设置',
        parent: windowManager.get('main'),
        modal: false
    });
    settingsWin.loadFile('settings.html');
});

三、窗口间通信 #

3.1 主进程转发消息 #

javascript
// 窗口间通信 - 通过主进程转发
ipcMain.on('window-message', (event, { target, channel, data }) => {
    const targetWin = windowManager.get(target);
    if (targetWin) {
        targetWin.webContents.send(channel, data);
    }
});

// 渲染进程发送
ipcRenderer.send('window-message', {
    target: 'settings',
    channel: 'update-config',
    data: { theme: 'dark' }
});

3.2 广播消息 #

javascript
// 主进程广播
function broadcastToAll(channel, data) {
    windowManager.broadcast(channel, data);
}

// 示例:主题变化时通知所有窗口
nativeTheme.on('updated', () => {
    const isDark = nativeTheme.shouldUseDarkColors;
    windowManager.broadcast('theme-changed', { isDark });
});

3.3 窗口状态同步 #

javascript
// 共享状态管理
const sharedState = {
    theme: 'light',
    language: 'zh-CN',
    user: null
};

// 更新状态并广播
function updateState(key, value) {
    sharedState[key] = value;
    windowManager.broadcast('state-updated', { key, value });
}

// IPC 处理
ipcMain.handle('get-state', () => {
    return sharedState;
});

ipcMain.handle('set-state', (event, { key, value }) => {
    updateState(key, value);
    return true;
});

四、窗口状态持久化 #

4.1 保存窗口状态 #

javascript
// windowState.js
const { app } = require('electron');
const fs = require('fs');
const path = require('path');

const stateFile = path.join(app.getPath('userData'), 'window-states.json');

class WindowStateManager {
    constructor() {
        this.states = this.load();
    }

    load() {
        try {
            const data = fs.readFileSync(stateFile, 'utf-8');
            return JSON.parse(data);
        } catch {
            return {};
        }
    }

    save() {
        fs.writeFileSync(stateFile, JSON.stringify(this.states, null, 2));
    }

    getState(name) {
        return this.states[name] || this.getDefaultState();
    }

    setState(name, state) {
        this.states[name] = state;
        this.save();
    }

    getDefaultState() {
        return {
            width: 800,
            height: 600,
            x: undefined,
            y: undefined,
            isMaximized: false,
            isFullScreen: false
        };
    }
}

module.exports = new WindowStateManager();

4.2 应用窗口状态 #

javascript
// 创建窗口时应用保存的状态
function createWindow(name, options) {
    const savedState = windowStateManager.getState(name);
    
    const win = new BrowserWindow({
        ...options,
        width: savedState.width,
        height: savedState.height,
        x: savedState.x,
        y: savedState.y
    });

    if (savedState.isMaximized) {
        win.maximize();
    }

    if (savedState.isFullScreen) {
        win.setFullScreen(true);
    }

    // 保存状态
    const saveState = () => {
        if (win.isMaximized() || win.isFullScreen()) {
            windowStateManager.setState(name, {
                ...savedState,
                isMaximized: win.isMaximized(),
                isFullScreen: win.isFullScreen()
            });
        } else {
            const [width, height] = win.getSize();
            const [x, y] = win.getPosition();
            windowStateManager.setState(name, {
                width,
                height,
                x,
                y,
                isMaximized: false,
                isFullScreen: false
            });
        }
    };

    win.on('close', saveState);
    win.on('moved', saveState);
    win.on('resized', saveState);

    return win;
}

五、模态窗口 #

5.1 创建模态窗口 #

javascript
// 模态窗口 - 阻塞父窗口
function createModalWindow(parent, options) {
    const modal = new BrowserWindow({
        ...options,
        parent: parent,
        modal: true,
        show: false
    });

    modal.once('ready-to-show', () => {
        modal.show();
    });

    return modal;
}

// 使用
ipcMain.handle('show-confirm', async (event, message) => {
    const parent = BrowserWindow.fromWebContents(event.sender);
    
    return new Promise((resolve) => {
        const modal = createModalWindow(parent, {
            width: 400,
            height: 200,
            title: '确认'
        });

        modal.loadFile('confirm.html');
        
        ipcMain.once('confirm-result', (e, result) => {
            modal.close();
            resolve(result);
        });
    });
});

5.2 非阻塞对话框 #

javascript
// 使用原生对话框代替模态窗口
const { dialog } = require('electron');

async function showConfirm(parent, message) {
    const result = await dialog.showMessageBox(parent, {
        type: 'question',
        buttons: ['确定', '取消'],
        title: '确认',
        message: message
    });
    
    return result.response === 0;
}

六、窗口类型示例 #

6.1 主窗口 #

javascript
function createMainWindow() {
    const win = windowManager.create('main', {
        width: 1024,
        height: 768,
        minWidth: 800,
        minHeight: 600,
        title: '我的应用',
        show: false
    });

    win.loadFile('index.html');

    win.once('ready-to-show', () => {
        win.show();
    });

    // 处理关闭(最小化到托盘)
    win.on('close', (event) => {
        if (!app.isQuitting) {
            event.preventDefault();
            win.hide();
        }
    });

    return win;
}

6.2 设置窗口 #

javascript
function createSettingsWindow() {
    if (windowManager.has('settings')) {
        windowManager.get('settings').focus();
        return;
    }

    const win = windowManager.create('settings', {
        width: 600,
        height: 500,
        title: '设置',
        parent: windowManager.get('main'),
        resizable: false,
        maximizable: false,
        fullscreenable: false
    });

    win.loadFile('settings.html');

    return win;
}

6.3 关于窗口 #

javascript
function createAboutWindow() {
    const win = windowManager.create('about', {
        width: 400,
        height: 300,
        title: '关于',
        parent: windowManager.get('main'),
        modal: false,
        resizable: false,
        maximizable: false,
        fullscreenable: false
    });

    win.loadFile('about.html');

    return win;
}

6.4 工具窗口 #

javascript
function createDevToolsWindow() {
    const win = windowManager.create('devtools', {
        width: 800,
        height: 600,
        title: '开发者工具',
        autoHideMenuBar: true
    });

    win.loadFile('devtools.html');

    return win;
}

七、窗口组管理 #

7.1 窗口组 #

javascript
class WindowGroup {
    constructor() {
        this.groups = new Map();
    }

    addToGroup(groupName, windowName) {
        if (!this.groups.has(groupName)) {
            this.groups.set(groupName, new Set());
        }
        this.groups.get(groupName).add(windowName);
    }

    closeGroup(groupName) {
        const group = this.groups.get(groupName);
        if (group) {
            for (const name of group) {
                windowManager.close(name);
            }
        }
    }

    hideGroup(groupName) {
        const group = this.groups.get(groupName);
        if (group) {
            for (const name of group) {
                windowManager.hide(name);
            }
        }
    }
}

const windowGroup = new WindowGroup();

// 使用
windowGroup.addToGroup('editors', 'editor-1');
windowGroup.addToGroup('editors', 'editor-2');
windowGroup.closeGroup('editors');

7.2 标签页式窗口 #

javascript
class TabbedWindowManager {
    constructor() {
        this.tabs = new Map();
        this.activeTab = null;
    }

    createTab(id, options) {
        const win = new BrowserWindow({
            ...options,
            show: false
        });

        this.tabs.set(id, win);

        win.on('closed', () => {
            this.tabs.delete(id);
        });

        return win;
    }

    switchTo(id) {
        if (this.activeTab) {
            this.tabs.get(this.activeTab)?.hide();
        }

        const win = this.tabs.get(id);
        if (win) {
            win.show();
            this.activeTab = id;
        }
    }

    closeTab(id) {
        const win = this.tabs.get(id);
        if (win) {
            win.close();
        }
    }
}

八、窗口动画 #

8.1 淡入淡出 #

javascript
// Windows/macOS 窗口淡入
function fadeIn(win, duration = 300) {
    win.setOpacity(0);
    win.show();

    const steps = 20;
    const interval = duration / steps;
    let step = 0;

    const timer = setInterval(() => {
        step++;
        win.setOpacity(step / steps);

        if (step >= steps) {
            clearInterval(timer);
        }
    }, interval);
}

// 淡出
function fadeOut(win, duration = 300) {
    const steps = 20;
    const interval = duration / steps;
    let step = steps;

    const timer = setInterval(() => {
        step--;
        win.setOpacity(step / steps);

        if (step <= 0) {
            clearInterval(timer);
            win.hide();
        }
    }, interval);
}

8.2 缩放动画 #

javascript
// 窗口缩放动画
function animateResize(win, targetWidth, targetHeight, duration = 300) {
    const [currentWidth, currentHeight] = win.getSize();
    const steps = 20;
    const interval = duration / steps;
    let step = 0;

    const widthStep = (targetWidth - currentWidth) / steps;
    const heightStep = (targetHeight - currentHeight) / steps;

    const timer = setInterval(() => {
        step++;
        const newWidth = Math.round(currentWidth + widthStep * step);
        const newHeight = Math.round(currentHeight + heightStep * step);
        win.setSize(newWidth, newHeight);

        if (step >= steps) {
            clearInterval(timer);
            win.setSize(targetWidth, targetHeight);
        }
    }, interval);
}

九、最佳实践 #

9.1 延迟创建 #

javascript
// 按需创建窗口,不要一次性创建所有窗口
const windowCreators = {
    settings: null,
    about: null
};

function openSettings() {
    if (!windowCreators.settings) {
        windowCreators.settings = createSettingsWindow;
    }
    windowCreators.settings();
}

9.2 窗口复用 #

javascript
// 复用窗口而不是销毁重建
function getOrCreateWindow(name, creator) {
    let win = windowManager.get(name);
    
    if (!win || win.isDestroyed()) {
        win = creator();
    } else {
        win.focus();
    }
    
    return win;
}

9.3 内存管理 #

javascript
// 及时清理窗口引用
win.on('closed', () => {
    // 清理事件监听器
    win.removeAllListeners();
    
    // 清理引用
    windowManager.windows.delete(name);
    
    // 清理相关资源
    cleanupWindowResources(name);
});

十、总结 #

10.1 核心要点 #

要点 说明
窗口管理器 统一管理所有窗口
窗口通信 通过主进程转发或广播
状态持久化 保存和恢复窗口状态
模态窗口 阻塞父窗口的对话框
窗口复用 避免重复创建

10.2 下一步 #

现在你已经掌握了窗口管理技术,接下来让我们学习 菜单与托盘,深入了解应用菜单和系统托盘的实现!

最后更新:2026-03-28