乐闻世界logo
搜索文章和话题

Electron 多窗口管理和通信

2月18日 10:40

Electron 应用经常需要创建多个窗口来实现复杂的功能,如主窗口、设置窗口、弹窗等。本文将详细介绍 Electron 中的多窗口管理和窗口间通信。

创建多个窗口

1. 基本窗口创建

javascript
// main.js const { app, BrowserWindow } = require('electron') let mainWindow let settingsWindow app.whenReady().then(() => { createMainWindow() }) function createMainWindow() { mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js') } }) mainWindow.loadFile('index.html') mainWindow.on('closed', () => { mainWindow = null }) } function createSettingsWindow() { if (settingsWindow) { settingsWindow.focus() return } settingsWindow = new BrowserWindow({ width: 600, height: 400, parent: mainWindow, // 设置为主窗口的子窗口 modal: true, // 模态窗口 webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js') } }) settingsWindow.loadFile('settings.html') settingsWindow.on('closed', () => { settingsWindow = null }) }

2. 窗口类型

javascript
// 主窗口 const mainWindow = new BrowserWindow({ width: 1200, height: 800, title: 'My App', icon: path.join(__dirname, 'icon.png') }) // 设置窗口 const settingsWindow = new BrowserWindow({ width: 600, height: 400, title: 'Settings', parent: mainWindow, modal: true }) // 弹出窗口 const popupWindow = new BrowserWindow({ width: 400, height: 300, parent: mainWindow, show: false, // 初始不显示 autoHideMenuBar: true }) // 工具窗口 const toolWindow = new BrowserWindow({ width: 300, height: 200, frame: false, // 无边框 alwaysOnTop: true, // 始终置顶 transparent: true // 透明背景 })

窗口管理

1. 窗口引用管理

javascript
// main.js const windows = new Map() function createWindow(id, options) { const win = new BrowserWindow(options) windows.set(id, win) win.on('closed', () => { windows.delete(id) }) return win } function getWindow(id) { return windows.get(id) } function closeWindow(id) { const win = getWindow(id) if (win) { win.close() } } function closeAllWindows() { windows.forEach((win, id) => { win.close() }) windows.clear() }

2. 窗口状态管理

javascript
// main.js const windowState = new Map() function saveWindowState(id, win) { const [width, height] = win.getSize() const [x, y] = win.getPosition() const isMaximized = win.isMaximized() const isFullScreen = win.isFullScreen() windowState.set(id, { width, height, x, y, isMaximized, isFullScreen }) } function restoreWindowState(id, win) { const state = windowState.get(id) if (state) { win.setSize(state.width, state.height) win.setPosition(state.x, state.y) if (state.isMaximized) { win.maximize() } if (state.isFullScreen) { win.setFullScreen(true) } } } // 使用 app.whenReady().then(() => { const win = createWindow('main', { width: 1200, height: 800 }) restoreWindowState('main', win) win.on('close', () => { saveWindowState('main', win) }) })

3. 窗口生命周期管理

javascript
// main.js app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createMainWindow() } }) // 防止意外关闭 app.on('before-quit', (event) => { const windows = BrowserWindow.getAllWindows() const hasUnsavedChanges = windows.some(win => win.webContents.executeJavaScript('hasUnsavedChanges()') ) if (hasUnsavedChanges) { event.preventDefault() // 提示用户保存 } })

窗口间通信

1. 主进程作为中介

javascript
// main.js const { ipcMain } = require('electron') // 从窗口 A 发送到窗口 B ipcMain.on('send-to-window-b', (event, data) => { const windowB = getWindow('window-b') if (windowB) { windowB.webContents.send('message-from-a', data) } }) // 广播到所有窗口 ipcMain.on('broadcast', (event, data) => { const windows = BrowserWindow.getAllWindows() windows.forEach(win => { if (win !== BrowserWindow.fromWebContents(event.sender)) { win.webContents.send('broadcast-message', data) } }) })

2. 直接窗口通信

javascript
// main.js // 获取特定窗口 function sendToWindow(windowId, channel, data) { const win = getWindow(windowId) if (win) { win.webContents.send(channel, data) } } // 从渲染进程调用 ipcMain.handle('send-to-window', (event, { windowId, channel, data }) => { sendToWindow(windowId, channel, data) })

3. 使用事件总线

javascript
// main.js const EventEmitter = require('events') const eventBus = new EventEmitter() // 订阅事件 eventBus.on('window-event', (data) => { console.log('Received event:', data) }) // 发布事件 eventBus.emit('window-event', { message: 'Hello' }) // 在窗口间使用 ipcMain.on('window-a-event', (event, data) => { eventBus.emit('window-a-event', data) }) ipcMain.on('window-b-event', (event, data) => { eventBus.emit('window-b-event', data) })

窗口数据共享

1. 使用主进程存储

javascript
// main.js const sharedData = new Map() ipcMain.handle('set-shared-data', (event, { key, value }) => { sharedData.set(key, value) return true }) ipcMain.handle('get-shared-data', (event, key) => { return sharedData.get(key) }) ipcMain.handle('delete-shared-data', (event, key) => { return sharedData.delete(key) })

2. 使用 localStorage

javascript
// renderer.js // 设置数据 localStorage.setItem('shared-key', JSON.stringify(data)) // 获取数据 const data = JSON.parse(localStorage.getItem('shared-key')) // 监听变化 window.addEventListener('storage', (event) => { if (event.key === 'shared-key') { const newData = JSON.parse(event.newValue) // 处理数据变化 } })

3. 使用 IndexedDB

javascript
// renderer.js const request = indexedDB.open('SharedDB', 1) request.onupgradeneeded = (event) => { const db = event.target.result const objectStore = db.createObjectStore('sharedData', { keyPath: 'key' }) } request.onsuccess = (event) => { const db = event.target.result // 添加数据 const transaction = db.transaction(['sharedData'], 'readwrite') const objectStore = transaction.objectStore('sharedData') objectStore.add({ key: 'myKey', value: 'myValue' }) // 获取数据 const getRequest = objectStore.get('myKey') getRequest.onsuccess = (event) => { console.log(event.target.result) } }

窗口同步

1. 状态同步

javascript
// main.js const windowStates = new Map() ipcMain.on('window-state-change', (event, state) => { const windowId = getWindowId(event.sender) windowStates.set(windowId, state) // 通知其他窗口 const windows = BrowserWindow.getAllWindows() windows.forEach(win => { if (win !== BrowserWindow.fromWebContents(event.sender)) { win.webContents.send('window-state-update', { windowId, state }) } }) })

2. 数据同步

javascript
// main.js ipcMain.on('data-update', (event, data) => { // 保存数据 saveData(data) // 通知所有窗口 const windows = BrowserWindow.getAllWindows() windows.forEach(win => { win.webContents.send('data-updated', data) }) })

3. 操作同步

javascript
// main.js ipcMain.on('perform-action', (event, action) => { // 执行操作 const result = performAction(action) // 通知所有窗口 const windows = BrowserWindow.getAllWindows() windows.forEach(win => { win.webContents.send('action-performed', { action, result }) }) })

窗口布局管理

1. 窗口位置管理

javascript
// main.js function arrangeWindows() { const windows = BrowserWindow.getAllWindows() const screenWidth = require('electron').screen.getPrimaryDisplay().workAreaSize.width const screenHeight = require('electron').screen.getPrimaryDisplay().workAreaSize.height windows.forEach((win, index) => { const [width, height] = win.getSize() const x = (index % 2) * (screenWidth / 2) const y = Math.floor(index / 2) * (screenHeight / 2) win.setPosition(x, y) }) }

2. 窗口大小管理

javascript
// main.js function resizeWindows(width, height) { const windows = BrowserWindow.getAllWindows() windows.forEach(win => { win.setSize(width, height) }) } function maximizeAllWindows() { const windows = BrowserWindow.getAllWindows() windows.forEach(win => { win.maximize() }) } function minimizeAllWindows() { const windows = BrowserWindow.getAllWindows() windows.forEach(win => { win.minimize() }) }

3. 窗口焦点管理

javascript
// main.js function focusWindow(windowId) { const win = getWindow(windowId) if (win) { win.focus() } } function focusNextWindow() { const windows = BrowserWindow.getAllWindows() const focusedWindow = BrowserWindow.getFocusedWindow() if (focusedWindow) { const currentIndex = windows.indexOf(focusedWindow) const nextIndex = (currentIndex + 1) % windows.length windows[nextIndex].focus() } }

最佳实践

1. 窗口创建优化

javascript
// main.js // 延迟创建窗口 function createWindowLazy(id, options) { if (getWindow(id)) { return getWindow(id) } const win = new BrowserWindow({ show: false, // 初始不显示 ...options }) win.once('ready-to-show', () => { win.show() }) windows.set(id, win) return win }

2. 内存管理

javascript
// main.js // 关闭不活跃的窗口 function cleanupInactiveWindows() { const windows = BrowserWindow.getAllWindows() const now = Date.now() windows.forEach(win => { const lastActive = win.getLastFocusedWebContents()?.getLastActiveTime() || 0 const inactiveTime = now - lastActive if (inactiveTime > 30 * 60 * 1000) { // 30分钟 win.close() } }) } // 定期清理 setInterval(cleanupInactiveWindows, 5 * 60 * 1000) // 5分钟

3. 错误处理

javascript
// main.js function safeCreateWindow(id, options) { try { const win = new BrowserWindow(options) windows.set(id, win) win.on('unresponsive', () => { console.error(`Window ${id} became unresponsive`) }) win.on('responsive', () => { console.log(`Window ${id} is responsive again`) }) win.on('crashed', (event, killed) => { console.error(`Window ${id} crashed. Killed: ${killed}`) // 尝试恢复窗口 recreateWindow(id, options) }) return win } catch (error) { console.error(`Failed to create window ${id}:`, error) return null } }

常见问题

Q: 如何防止窗口被关闭?A: 监听 beforeunload 事件:

javascript
// renderer.js window.addEventListener('beforeunload', (event) => { if (hasUnsavedChanges()) { event.preventDefault() event.returnValue = false } })

Q: 如何在窗口间传递大量数据?A: 使用主进程作为中介,或者使用共享存储如 IndexedDB。

Q: 如何实现窗口拖拽?A: 使用 -webkit-app-region CSS 属性:

css
.title-bar { -webkit-app-region: drag; }

Q: 如何实现窗口透明效果?A: 设置 transparent: true 并使用 CSS:

javascript
const win = new BrowserWindow({ transparent: true, frame: false })

标签:Electron