Similar to your other SO Question, you will need to become familiar with the below Electron Browser Window - Instance Methodsfor 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 modulesconst electronApp = require('electron').app;const electronBrowserWindow = require('electron').BrowserWindow;const electronIpcMain = require('electron').ipcMain;// Import required Node modulesconst nodePath = require('path');// Prevent garbage collectionlet 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 modulesconst contextBridge = require('electron').contextBridge;const ipcRenderer = require('electron').ipcRenderer;// White-listed channelsconst 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 processcontextBridge.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>