Similar to your other SO Question, you will need to become familiar with the below Electron Browser Window - Instance Methods
for more information.
On window creation, we tell the window what state it is in. EG: Maximised or restored. This 'state' is sent via IPC (via our preload.js
script) to the render side Javascript to either display or hide the correct maximise / restore buttons.
Following this, we just listen for any render side title bar button clicks. Once 'clicked', send a message via IPC to the main process to control the state of the window. Depending on the request ('maximise' or 'restore'), send a message back (via IPC) to the render process to either 'show' or 'hide' (via the CSS display attribute) the correct maximise / restore button.
Here, we set the window frame state to false
, load the index.html
file, tell the render process (via IPC) the state of our window (restored in this case) and then finally show the window.
main.js
(main process)
// Import required electron modules
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
// Import required Node modules
const nodePath = require('path');
// Prevent garbage collection
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
frame: false,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile(nodePath.join(__dirname, 'index.html'))
// Below boolean value could be retrieved from saved application setting (json file) on start-up
.then(() => { window.webContents.send('maximised', false); })
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ---
electronIpcMain.on('minimise', (event) => {
window.minimize();
})
electronIpcMain.on('maximise', (event) => {
window.maximize();
window.webContents.send('maximised', true);
})
electronIpcMain.on('restore', (event) => {
window.restore();
window.webContents.send('maximised', false);
})
electronIpcMain.on('close', (event) => {
window.close();
})
Set our channel names used to manage the window state.
preload.js
(main process)
// Import the necessary Electron modules
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels
const ipc = {
'channels': {
// From render to main
'send': [
'minimise',
'maximise',
'restore',
'close'
],
// From main to render
'receive': [
'maximised'
],
// From main to render (once)
'receiveOnce': [],
// From render to main and back again
'sendReceive': []
}
};
// Exposed protected methods in the render process
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods
'ipcRenderer', {
// From render to main
send: (channel, args) => {
if (ipc.channels.send.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render
receive: (channel, listener) => {
if (ipc.channels.receive.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From main to render (once)
receiveOnce: (channel, listener) => {
if (ipc.channels.receiveOnce.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.once(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again
invoke: (channel, args) => {
if (ipc.channels.sendReceive.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Use of this preload.js
script is as follows.
/**
*
* Main --> Render
* ---------------
* Main: window.webContents.send('channel', data); // Data is optional.
* Render: window.ipcRenderer.receive('channel', (data) => { methodName(data); });
*
* Main --> Render (Once)
* ----------------------
* Main: window.webContents.send('channel', data); // Data is optional.
* Render: window.ipcRenderer.receiveOnce('channel', (data) => { methodName(data); });
*
* Render --> Main
* ---------------
* Render: window.ipcRenderer.send('channel', data); // Data is optional.
* Main: electronIpcMain.on('channel', (event, data) => { methodName(data); })
*
* Render --> Main (Once)
* ----------------------
* Render: window.ipcRenderer.send('channel', data); // Data is optional.
* Main: electronIpcMain.once('channel', (event, data) => { methodName(data); })
*
* Render --> Main (Value) --> Render
* ----------------------------------
* Render: window.ipcRenderer.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', (event, data) => { return someMethod(data); });
*
* Render --> Main (Promise) --> Render
* ------------------------------------
* Render: window.ipcRenderer.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', async (event, data) => {
* return await myPromise(data)
* .then((result) => { return result; })
* });
*
* Main: function myPromise(data) { return new Promise((resolve, reject) => { ... }); }
*
*/
Finally, let's listen for and send messages to the main process (via our preload.js
script) to manage the window state.
index.html
(render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>my app</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
</head>
<body style="margin: 0; padding: 0;">
<!-- Simple use of flexbox styling + prevent user from selecting title bar contents -->
<div style="display: flex; justify-content: space-between; padding: 0.25em; background-color: grey; -webkit-user-select: none;">
<!-- Add title bar drag functionality by using "-webkit-app-region: drag;" -->
<span style="flex: 1 0 auto; -webkit-app-region: drag;">
<img src="" alt="logo" style="width: 22px; height: 22px;">
<span class="title-text">My App - Some Page</span>
</span>
<!-- Prevent title bar dragging by buttons by using "-webkit-app-region: no-drag;" -->
<span style="flex: 0 1 auto; -webkit-app-region: no-drag;">
<input type="button" id="minimise_button" value="-">
<input type="button" id="maximise_button" value="◻">
<input type="button" id="restore_button" value="R">
<input type="button" id="close_button" value="×">
</span>
</div>
<div class="application-container">...</div>
</body>
<script>
// Declare these elements as we use them more than once
let maximise_button = document.getElementById('maximise_button');
let restore_button = document.getElementById('restore_button');
// Minimise button functionality
document.getElementById('minimise_button').addEventListener('click', () => {
window.ipcRenderer.send('minimise');
});
// Maximise button functionality
maximise_button.addEventListener('click', () => {
window.ipcRenderer.send('maximise');
});
// Restore button functionality
restore_button.addEventListener('click', () => {
window.ipcRenderer.send('restore');
});
// Close button functionality
document.getElementById('close_button').addEventListener('click', () => {
window.ipcRenderer.send('close');
});
// Toggle css "display" attribute of maximise & restore buttons depending on state of window
window.ipcRenderer.receive('maximised', (state) => {
maximise_button.style.display = (state) ? 'none' : 'inline-block';
restore_button.style.display = (state) ? 'inline-block' : 'none';
});
</script>
</html>