Multiple Window Electron App
Electron apps seem classically one window, with one instance of a web application running. This is frustrating, even if you just want multiple instances of the same app running.
In my project, I wanted to easily open multiple instances similar to Visual Studio Code’s File ≫ New Window.
This example is based on Electron React Boilerplate as the initial project scaffolding.
Under app/main.dev.js
there’s logic for just one mainWindow
— first expand that to be a JavaScript Set
of windows. Replace mainWindow
instance of BrowserWindow
with a new reference to hold all windows
const windows = new Set();
In the createWindow()
method, each time a new window is created:
- Offset new widow’s position slightly (24 pixels)
- When the window is closed, remove it from the
windows
set - When the window is focused, update the menu
export const createWindow = async () => {
if (
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
) {
await installExtensions();
} let x, y; const currentWindow = BrowserWindow.getFocusedWindow(); if (currentWindow) {
const [currentWindowX, currentWindowY] = currentWindow.getPosition();
x = currentWindowX + 24;
y = currentWindowY + 24;
} let newWindow = new BrowserWindow({
show: false,
width: 1200,
height: 812,
x,
y,
webPreferences: {
nodeIntegration: true
}
}); newWindow.loadURL(`file://${__dirname}/app.html`); newWindow.webContents.on('did-finish-load', () => {
if (!newWindow) {
throw new Error('"newWindow" is not defined');
}
if (process.env.START_MINIMIZED) {
newWindow.minimize();
} else {
newWindow.show();
newWindow.focus();
}
}); newWindow.on('closed', () => {
windows.delete(newWindow);
newWindow = null;
}); newWindow.on('focus', () => {
const menuBuilder = new MenuBuilder(newWindow);
menuBuilder.buildMenu();
}); windows.add(newWindow);
return newWindow;
};
Finally, update the app’s activate
event handler for macOS:
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (windows.size === 0) createWindow();
});
Of course, you’ll need some way to trigger launching the new window, such as inter process communication or simply adding to the app’s menu.
Menu
Open the new window by creating a File menu group, with accelerator keyboard shortcut keys.
Import the createWindow
function in menu.js
import { createWindow } from './main.dev';
Create the Darwin menu implementation:
const subMenuFile = {
label: 'File',
submenu: [
{
label: 'New Window',
accelerator: 'Shift+Command+N',
click: () => {
createWindow();
}
}
]
};
Remember to add the new subMenuFile
menu group to the Darwin template
return [
subMenuAbout,
subMenuFile,
subMenuEdit,
subMenuView,
subMenuWindow,
subMenuHelp
];
…and corresponding default template implementation:
{
label: '&File',
submenu: [
{
label: 'New',
accelerator: 'Ctrl+N',
click: () => {
this.mainWindow.send('file-new');
}
},
{
label: 'New Window',
accelerator: 'Shift+Ctrl+N',
click: () => {
createWindow();
}
},
{ type: 'separator' },
...
Now you can easily launch multiple windows of your application.
Continue to part two, where I’ll demonstrate sending messages between windows using inter process communication for sharing data such as complex clipboard copy and paste: