除了使用new关键字实例化实例外,BrowserWindow模块还有自己的方法。我们可以使用BrowserWindow.getFocusedWindow()获得对用户当前正在使用的窗口的引用。当应用程序第一次准备好并调用createWindow()时,没有一个焦点窗口,`BrowserWindow.getFocusedWindow()返回undefined。如果有一个窗口,我们调用它的getWindow()方法,该方法返回一个此窗口的x和y坐标的数组。我们将把这些值存储在条件块之外的两个变量中,并将它们传递给BrowserWindow构造函数。如果它们仍然是未定义的(例如,没有焦点窗口),那么Electron将使用缺省值,就像我们实现此功能之前所做的那样。图5.4显示了与第一个窗口相比的第二个窗口偏移量。
图5.4 新窗口偏移当前窗口
这不是实现此功能的唯一方法。或者,您可以跟踪初始的x和y位置,并在每个新窗口上增加这些值。或者,您可以为默认的x和y值添加一点随机性,这样每个窗口都是稍微偏移量。我把这些方法留给读者作为练习。
结合macOS在macOS中,即使所有的窗口都关闭了,许多(但不是所有)应用程序仍然保持打开状态。例如,如果您关闭了Chrome中的所有窗口,应用程序在dock中仍然出于活动状态,并且仍然出现在应用程序切换器中。Fire Sale不能做到这点。
在前几张章中,这可能是可以接受的。我们只有一个窗口,无法创建其他窗口。在本节中,我们只允许应用程序在macOS中保持打开状态。默认情况下,当Electron触发它的window-all-closed事件时,它将退出应用程序。如果我们想要阻止这种行为,我们必须监听这个事件,并且在macOS上运行时有条件地阻止它关闭。
列表5.11 在关闭所有窗口时保持应用程序的活动状态: ./app/main.js
app.on('window-all-closed', () => { if(process.platform === 'darwin') { //检查应用程序是否在macOS上运行 return false; //如果是,则返回false以防止默认操作 } app.quit(); //如果不是,则退出应用程序 });process对象由Node提供,不需要配置全局可用。process.platform返回当前执行应用程序的平台名称。在截至写作时间点,process.platform返回七个字符串之一: aix,darwin,freebsd,linux,openbsd,sunos或win32。Darwin是构建macOS的UNIX操作系统。在清单5.11中,我们检查了是否process.platform等于darwin,如果是,则应用程序正在macOS上运行,我们希望返回false以阻止默认操作的发生。
保持应用程序的活动是成功的一半,如果用户单击dock中的应用程序而没有打开窗口,会发生什么?在这种情况下,Fire Sale应该打开一个新窗口并显示给用户,如下所示。
图5.12 在应用程序打开时创建一个窗口,但没有窗口: ./app/main.js
app.on('activate', (event, hasVisibleWindows) => { //Electron提供了hasVisibleWindows参数,它将是一个布尔值。 if(!hasVisibleWindows) { createWindow(); } //如果用户激活应用程序时没有可见窗口,则创建一个。 });activate事件将两个参数传递给提供的回调函数。第一个是event对象,第二个是布尔值,如果任何窗口都可见,则返回true;如果所有窗口都关闭,则返回false.对于后者,我们调用本章前面编写的createWindow()函数。
activate事件只在macOS上触发,但是有很多原因可以解释为什么您可能选择让您的应用程序在Windows或Linux上保持打开状态,特别是如果应用程序正在运行后台进程,而您希望继续运行这些进程,即使该窗口被关闭。另一种可能性是,您的应用程序可以隐藏,或者使用全局快捷方式显示,或者从托盘或菜单栏中显示。我们将在后面的章节中实现这些。
通过这两个额外的事件,我们将Fire Sale从单窗口应用程序转换为支持多窗口的应用。这个清单显示了主进程当前状态的代码。
列表5.13 在主进程中实现多个窗口: ./app/main.js
const{ app, BrowserWindow,dialog } = require('electron'); const fs = require('fs'); const windows = new Set(); app.on('ready', () => { createWindow(); }); app.on('window-all-closed', () => { if(process.platform === 'darwin') { return false; } }); app.on('activate', (event, hasVisibleWindows) => { if(!hasVisibleWindows) { createWindow(); } }); const createWindow = exports.createWindow = () => { let x,y; const currentWindow = BrowserWindow.getFocusedWindow(); if(currentWindow) { const [ currentWindowX, currentWindowY ] = currentWindow.getPosition(); x = currentWindowX + 10; y = currentWindowY +10; } let newWindow = new BrowserWindow({ x, y, show: false, webPreferences: { // WebPreferences中的nodeIntegrationInWorker选项设置为true nodeIntegration: true } }); newWindow.loadFile('app/index.html'); newWindow.once('ready-to-show', () => { newWindow.show(); }); newWindow.on('closed', () => { windows.delete(newWindow); newWindow = null; }); windows.add(newWindow); return newWindow; }; const getFileFromUser = exports.getFileFromUser = (targetWindow) => { const files = dialog.showOpenDialog(targetWindow, { properties: ['openFile'], filters: [ { name: 'Text Files', extensions: ['txt'] }, { name: 'Markdown Files', extensions: ['md', 'markdown'] } ] }); if (files) { openFile(targetWindow, files[0]); } // A }; const openFile = (targetWindow, file) => { const content = fs.readFileSync(file).toString(); targetWindow.webContents.send('file-opened', file, content); // B }; 总结当创建具有多个窗口的Electron应用程序时,我们不能硬编码主进程发送数据的窗口。
我们可以使用Electron的remote模块向渲染器进程中的窗口请求对自身的引用,并在与主进程通信时发送该引用。
macOS上的应用程序并不总是在所有窗口都关闭时退出,我们可以使用Node的process对象来确定应用程序在那个平台上运行。
如果process.platform是darwin,则应用程序在macOS上运行。
在监听应用程序的windows-all-closed事件的函数中,返回false从而防止应用程序退出。
在macOS上,当用户单击dock图标时,应用程序会触发activate事件。