Author: Kurosaki

This paper mainly explains the Electron window API and some problems encountered in the development.

Although the official documentation is comprehensive, developing a commercial-grade desktop application requires a deep understanding of the entire Electron API to meet the various requirements.

1. Create a window

BrowserWindow is used to create or manage new browser Windows, and each BrowserWindow has a process to manage it.

1.1. Simply create Windows

const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com');
Copy the code

The effect is as follows:

1.1.2. The optimization

Problem: The electron BrowserWindow module is created with show:false not configured, and the default background is white. The window then requests HTML and a visual flicker appears.

To solve

const { BrowserWindow } = require('electron');
const win = new BrowserWindow({ show:false });

win.loadURL('https://github.com');

win.on('ready-to-show'.() = >{
    win.show();
})
Copy the code

There’s a big difference

1.2. Management window

An administrative window is equivalent to how much the main process can interfere with the window.

  • Window route jump
  • Window Opens a new window
  • Window size, location, etc
  • Window display
  • Window types (borderless window, parent window)
  • Inside the windowJavaScript 的 nodePermissions, preloading scripts, etc
  • .

All of these methods exist in the BrowserWindow module.

1.2.1. Managing the application creation window

The BrowserWindow module, when creating a window, returns instances of the window. These instances have a number of function methods that we use to manage and control the window.

Map objects are used here to store these window instances.

const BrowserWindowsMap = new Map<number, BrowserWindow>()
let mainWindowId: number;

const browserWindows = new BrowserWindow({ show:false })
browserWindows.loadURL('https://github.com')
browserWindows.once('ready-to-show'.() = > {
  browserWindows.show()
})
BrowserWindowsMap.set(browserWindow.id, browserWindow)
mainWindowId = browserWindow.id  // Record the current window as the main window
Copy the code

The window closes and the Map instance has to be deleted.

browserWindow.on('closed'.() = >{ BrowserWindowsMap? .delete(browserWindowID) })Copy the code

1.2.2. Managing Windows created by users

The main process can control many window behaviors, which will be listed in a subsequent article. The following is an example of how the main process control window creates a new window.

usenew-windowListen for new window creation

// Create window listener
browserWindow.webContents.on('new-window'.(event, url, frameName, disposition) = > {
  / * *@params {string} disposition* background-tab: foreground +click * foreground- TAB: foreground +click * foreground * /})Copy the code

Note: For an explanation of the Disposition field, move to the Electron document, the Electron source, the Chrome source

extensionnew-window

After experiments, not all new window creation, new-window can capture.

Windows opened in the following way can benew-windowEvent capture

window.open('https://github.com')
Copy the code
<a href='https://github.com' target='__blank'>link</a>
Copy the code

** New Windows created using BrowserWindow in the rendering process will not be caught by the new-window event **

const { BrowserWindow } = require('electron').remote
const win = new BrowserWindow()
win.loadURL('https://github.com')
Copy the code

EnableRemoteModule :true this method can also open a new window, but the main process’s new-window does not catch.

New-window new-window controls the creation of new Windows, and we can do a lot of things with that; Such as link verification, browser opening links and so on. The default browser opening link code is as follows:

import { shell } from 'electron'
function openExternal(url: string) {
  const HTTP_REGEXP = /^https? : \ \ / / /
  // Do not enable this function for non-HTTP protocols to prevent security problems caused by user-defined protocols
  if(! HTTP_REGEXP) {return false
  }
  try {
    await shell.openExternal(url, options)
    return true
  } catch (error) {
    console.error('open external error: ', error)
    return false}}// Create window listener
browserWindow.webContents.on('new-window'.(event, url, frameName, disposition) = > {
  if (disposition === 'foreground-tab') {
      // Block mouse clicks on links
      event.preventDefault()
      openExternal(url)
  }
})
Copy the code

_ about __shell_ module, can view the website www.electronjs.org/docs/api/sh… _

1.3. Close the window

** Close **** event and ****closed** event Close events are fired before the window is about to close, but before the beforeUnload and unload events of the DOM.

// The window registers the close event
win.on('close'.(event) = >{
	event.preventDefault()  // Prevents the window from closing
})
Copy the code

The closed event is emitted after the window is closed, but the window is already closed and there is no way to prevent it from closing by event.preventDefault().

win.on('closed', handler)
Copy the code

There are many apis for the main process to close Windows, but each has its own pros and cons.

1.3.1. win.close()

About the pros and cons of this API

  1. If the current window instance is registered and blockedcloseEvent, will not close the page, and willPreventing computer shutdown(you must manually force the exit);
  2. Close the page’s services, for examplewebsocketThe next time you open a window, the page in the window willTo render;
  3. Through thisAPIThe triggercloseIn the eventunloadbeforeunloadTrigger before, that’s how you do itTrigger popover when closing;

The full code is on Github:electron-playground

  1. Will beclosedEvent caught.

1.3.2. win.destroy()

  1. Force exit, ignorecloseEvent (i.e., unable to passevent.preventDefault()To stop);
  2. Close the page and the services in the page. The next time you open the window, the page in the window will be re-rendered.
  3. Will beclosedEvent caught.

1.3.3. win.hide()

This hidden window.

  1. Hiding the window triggershideandblurEvents can also pass throughevent.preventDefault()To prevent the
  2. Just hide the window and go throughwin.show(), can display the window, and will keep the original window, inside the service will not hang up

2. Hide and restore the main window

2.1. The main window

2.1.1. WhyThe main window?

An application has many Windows. One window is required as the main window. If the window is closed, the entire application is closed. Scenario: When the application has only one page, the user clicks the close button to hide the application instead of closing it. For example: other apps, such as wechat, QQ and other desktop end.

Using the window closing API mentioned above, we implement a main window hiding and restoring.

Modify the close event

let mainWindowId: number // Used to mark the main window ID

const browserWindow = new BrowserWindow()

// Record the main window ID
if(! mainWindowId) { mainWindowId = browserWindow.id } browserWindow.on('close'.event= > {
  // If the main window is closed, block
  if (browserWindow.id === mainWindowId) {
    event.preventDefault()
    browserWindow.hide()
  }
})
Copy the code

2.1.2. Restore the main window display

If you can hide, you can recover.

const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
  mainWindow.restore()
  mainWindow.show()
}
Copy the code

** mainwindow.show ()** method: shows Windows as the name suggests. _ Why is __mainwindow.restore ()_? _windows_ if __hide_ _ is not followed by __show_ method but only __restore_ method, the page will be hung and unusable

2.1.3. Forcibly close the main window

In some scenarios, a forcible exit may be required, with code attached:

const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
  mainWindowId = -1
  mainWindow.close()
}
Copy the code

Existing problems

We changed the behavior of the Electron window, and there were many scenarios where there were problems

Problem one: because it stoppedcloseEvent, result inTo turn it offCannot be closed whenThe main window, you can use the following code

app.on('before-quit'.() = > {
    closeMainWindow()
})
Copy the code

During macOS Linux WindowsEither way.

Problem two: To avoid startupMultiple applications

app.on('second-instance'.() = > {
  const mainWindow = BrowserWindowsMap.get(mainWindowId)
  if (mainWindow) {
    mainWindow.restore()
    mainWindow.show()
  }
})
Copy the code

During macOS Linux WindowsCan be under

Problem three: Start the application for the first time, try while the application is already running, or clickThe applicationdockTaskbar iconAnd reactivate it

app.on('activate'.() = > {
  if (mainWindow) {
    mainWindow.restore()
    mainWindow.show()
  }
})
Copy the code

Applies only to macOS

Problem four:Double-click on the tray iconOpen theAPP

tray.on('double-click'.() = > {
  if (mainWindow) {
    mainWindow.restore()
    mainWindow.show()
  }
})
Copy the code

In this way, the code for each link can be realized. The specific code can be seen in the link

3. Window focus and out of focus

3.1. The focus

3.1.1. Configuration when creating Windows

const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com')
Copy the code

Focusable :false Focusable :false in Windows focusable:false also means skipTaskbar: true. Setting focusable: false in Linux stops the window from interacting with WM, and the window will always be at the top;

The following is discussed only in the focusable:true case

const { BrowserWindow } = require('electron');
const win = new BrowserWindow() // focusable:true is the default
Copy the code

Listing the apis

3.1.2. Apis for focusing

API function
BrowserWindow.getFocusedWindow() To get the focused window
win.isFocused() Determines whether the window is focused
win.on('focus',handler) To monitor whether the window is focused
win.focus() Manual focus window

3.1.3. Other API side effects related to focus:

API function
win.show() Displays the window and focuses on the window
win.showInactive() Displays the window, but does not focus on the window

3.2 out of focus

3.2.1. About out of focusapi

API function
win.blur() Unfocus window
win.on('blur',cb) Listening out of focus

3.2.2. OtherapiSide effects related to loss of focus:

api function
win.hide() The window is hidden and an out-of-focus event is triggered

4. Window type

4.1. Bezel-less Windows

4.4.1. Describe

A frameless window is a window without a shell (including the window border, toolbar, etc.) that only contains the content of the web page

4.1.2. Achieve

Windows macOS Linux

const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ width: 800.height: 600.frame: false })
win.show()
Copy the code

Under macOS, there are different implementations, official documentation

4.1.3. macOSUnder the unique no border

  • configurationtitleBarStyle: 'hidden'

Returns a full-size content window with a hidden title bar, still with the standard window control button in the upper left corner (commonly known as “traffic light”)

// Create a window with no borders
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ titleBarStyle: 'hidden' })
win.show()
Copy the code

The effect is as follows:

  • configurationtitleBarStyle: 'hiddenInset'

Returns a different type of window with a hidden title bar, with a larger distance between the control button and the window border.

// Create a window with no borders
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })
win.show()
Copy the code

The effect is as follows:

configurationtitleBarStyle: 'customButtonsOnHover'

The effect is as follows:

4.1.4. The problem that the top of the window cannot be dragged

Although no border window, very beautiful, you can customize the title; But changing the default behavior at the top of the Electron window requires code that is compatible with it and does what it originally does.

This happens because, by default, bezel-less Windows are not draggable. The application needs to be inCSSSpecified in the-webkit-app-region: dragTo tell theElectronWhich areas are draggable (such as the standard title bar of the operating system) to use inside the draggable areas-webkit-app-region: no-dragSome areas can be excluded. Note that currently only rectangular shapes are supported.Complete documentation

Drag is implemented using -webkit-app-region: drag, but invalidates the internal click event. Set the click element to -webkit-app-region: no-drag. Specific details Electron issues

In order not to affect the business code within the window, here drag-and-drop code should be triggered in Preload.

The preload code runs before the window code runs

Core code:

// Insert a removable DOM at the top
function initTopDrag() {
  const topDiv = document.createElement('div') // Create a node
  topDiv.style.position = 'fixed' // Always at the top
  topDiv.style.top = '0'
  topDiv.style.left = '0'
  topDiv.style.height = '20px' // Drag only at the top 20px
  topDiv.style.width = '100%' / / width is 100%
  topDiv.style.zIndex = '9999' // Float in the outermost layer
  topDiv.style.pointerEvents = 'none' // For click penetration
  // @ts-ignore
  topDiv.style['-webkit-user-select'] = 'none' // Disable text selection
  // @ts-ignore
  topDiv.style['-webkit-app-region'] = 'drag' / / drag
  document.body.appendChild(topDiv) // Add a node
}

window.addEventListener('DOMContentLoaded'.function onDOMContentLoaded() {
    initTopDrag()
})
Copy the code

Just reference Preload when creating the window

const path = require('path')
const { BrowserWindow } = require('electron')

const BaseWebPreferences = {
  nodeIntegration: true.preload: path.resolve(__dirname, './windowType.js'), // The preload.js path is referenced here
}

// Main window code
const win = new BrowserWindow({ webPreferences: BaseWebPreferences, frame: false.titleBarStyle: 'hiddenInset' })
win.loadURL('https://github.com')
Copy the code

Can achieve window top drag_tips: If the window is open __devtools_ Windows can also be dragged, but the drag experience is not good

4.2. Parent-child window

The so-called father and son window, that is, the child window is always above the father window, as long as the child window exists, even if the location is not above the father window, it is unable to operate the father window

const { BrowserWindow } = require('electron')

let top = new BrowserWindow()
let child = new BrowserWindow({ parent: top })
child.show()
top.show()
Copy the code

The communication between parent and child Windows is introduced in the section of communication between Windows. Get the class BrowserWindowProxy of the parent window through getParentWindow, and communicate through Win. postMessage(Message,targetOrigin)

4.3. Modal window

Modal window is also a parent window, but the presentation will be different

const { BrowserWindow } = require('electron')

let top = new BrowserWindow()
let child = new BrowserWindow({ parent: top, modal: true.show: false })
child.loadURL('https://github.com')
child.once('ready-to-show'.() = > {
  child.show()
})
Copy the code

5. Communication between Windows

Implementing window communication must not affect the injection of business code, JDK, and so on within the window

5.1. Main process intervention mode

The main process can interfere with the renderer process to create a new window, as long as webContents listens on new-window while the window is being created

import path from 'path'
import { PRELOAD_FILE } from 'app/config'
import { browserWindow } from 'electron';

const BaseWebPreferences: Electron.BrowserWindowConstructorOptions['webPreferences'] = {
  nodeIntegration: true.webSecurity: false.preload: path.resolve(__dirname, PRELOAD_FILE),
}


// Create window listener
browserWindow.webContents.on('new-window'.(event, url, frameName, disposition) = > {
    event.preventDefault()
    // Create a window through BrowserWindow
    const win = new BrowserWindow({ 
      show:false.webPreferences: {
        ...BaseWebPreferences,
        additionalArguments: [`--parentWindow=${browserWindow.id}`] // Pass the id of the parent window}}); win.loadURl(url); win.once('ready-to-show'.() = >{
        win.show()
    })
})
Copy the code

Argv is an array of strings that can be parsed using yargs

Preload. Js code

import { argv } from 'yargs'
console.log(argv);
Copy the code

Got to the parent windowid, encapsulate the communication code and mount it towindow 上

/** * this is preload for the window communication example, * preload execution order before the window JS execution order */
import { ipcRenderer, remote } from 'electron'
const { argv } = require('yargs')

const { BrowserWindow } = remote

// The parent window listens for child window events
ipcRenderer.on('communication-to-parent'.(event, msg) = > {
  alert(msg)
})

const { parentWindowId } = argv
if(parentWindowId ! = ='undefined') {
  const parentWindow = BrowserWindow.fromId(parentWindowId as number)
  // Mount to window
  // @ts-ignore
  window.send = (params: any) = > {
    parentWindow.webContents.send('communication-to-parent', params)
  }
}
Copy the code

Try it:This method can achieve communication, but it is too cumbersome.

5.2. Parent-child window communication

The parent window id is not passed by additionalArguments, and the parent window is called by window.parent in the child window

browserWindow.webContents.on('new-window'.(event, url, frameName, disposition) = > {
    event.preventDefault()
      
    // Create a window through BrowserWindow
    const win = new BrowserWindow({ 
        show:false.webPreferences:BaseWebPreferences,
        parent:browserWindow // Add parent window
      });
    win.loadURl(url);
    win.once('ready-to-show'.() = >{
        win.show()
    })
    
})
Copy the code

Disadvantage: Child window is always above parent window.

const path = require('path')
const { BrowserWindow } = require('electron')

const BaseWebPreferences = {
  // // Integrated node
  nodeIntegration: true.// // Disable the same-origin policy
  // webSecurity: false,
  // Preload scripts via absolute address injection
  preload: path.resolve(__dirname, './communication2.js'),}// Main window code
const parent = new BrowserWindow({ webPreferences: BaseWebPreferences, left: 100.top: 0 })
parent.loadURL(
  'file:///' + path.resolve(__dirname, '.. /playground/index.html#/demo/communication-part2/main'),
)
parent.webContents.on('new-window'.(event, url, frameName, disposition) = > {
  // Block the default event
  event.preventDefault()
  // Create a window through BrowserWindow
  // Child window code
  const son = new BrowserWindow({
    webPreferences: BaseWebPreferences,
    parent,
    width: 400.height: 400.alwaysOnTop: false,})// son.webContents.openDevTools();
  son.loadURL(
    'file:///' +
      path.resolve(__dirname, '.. /playground/index.html#/demo/communication-part2/client'),)})Copy the code

preload.js

import { remote, ipcRenderer } from 'electron'

// The parent window listens for child window events
ipcRenderer.on('communication-to-parent'.(event, msg) = > {
  alert(msg)
})

const parentWindow = remote.getCurrentWindow().getParentWindow()
// @ts-ignore
window.sendToParent = (params: any) = >
  parentWindow.webContents.send('communication-to-parent', params)

Copy the code

But it has to be a father-child window.

5.3. Usewindow.open

The ultimate way to

On the Web side, window.open returns a windowObjectReference, which implements postMessage. But at the Electron end, the window.open method is redefined; Creating a new window using window.open returns a BrowserWindowProxy object and provides a child window with limited functionality. MDN document Electron document

const  BrowserWindowProxy = window.open('https://github.com'.'_blank'.'nodeIntegration=no')
BrowserWindowProxy.postMessage(message, targetOrigin)
Copy the code

Windows. Open can be configured using options in BrowserWindow(Options).

6. Full screen, maximize, minimize, restore

6.1. The full screen

6.1.1. Go to full screen during creation

Configure new BrowserWindow({fullscreen:true})

const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ fullscreen:true.fullscreenable:true })
win.loadURL('https://github.com')
Copy the code

Use 6.1.2.APIEnter the full screen

Ensure that the current window is fullscreEnable :true for the following API to be used

  1. win.setFullScreen(flag), set the full-screen state;
  2. win.setSimpleFullScreen(flag).macOSSet up a simple full screen.

6.1.3. Obtaining the full-screen state

  1. win.fullScreen, to determine whether the current window is full-screen;
  2. win.isFullScreen().macOSUnique;
  3. win.isSimpleFullScreen().macOSUnique.

6.1.4. Monitoring of full-screen events

  1. reziseTrigger after adjusting window size;
  2. enter-full-screenTrigger when the window enters full-screen state;
  3. leave-full-screenTriggered when the window leaves the full-screen state;
  4. enter-html-full-screenTriggered when the window enters the full-screen state triggered by the HTML API;
  5. leave-html-full-screenTriggered when the window leaves the full-screen state triggered by the HTML API.

6.1.5. HTML APIThe window cannot be connected

const path = require('path')
const { BrowserWindow } = require('electron')
const BaseWebPreferences = { 
    nodeIntegration: true.preload: path.resolve(__dirname, './fullScreen.js'),};const win = new BrowserWindow({ webPreferences: BaseWebPreferences })
win.loadURL('file:///' + path.resolve(__dirname, '.. /playground/index.html#/demo/full-screen'))
Copy the code

It is possible to use the button to full screen and exit the full screen, but it is not possible to click 🚥 in the upper left corner and exit the full screen using the button. There’s no way to know if you’re in full screen or not.

The solution is to mount the win.setFullScreen(flag) method to the window and load the preload.js code

import { remote } from 'electron'

const setFullScreen = remote.getCurrentWindow().setFullScreen
const isFullScreen = remote.getCurrentWindow().isFullScreen

window.setFullScreen = setFullScreen
window.isFullScreen = isFullScreen
Copy the code

https://www.electronjs.org/docs/api/browser-window#winsetfullscreenflag isFullScreen _ setFullScreen document Document _https: / / www.electronjs.org/docs/api/browser-window#winisfullscreen

6.2. Maximize and minimize

6.2.1. Create a window configuration

Full API documentation

const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ minWidth:300.minHeight:300.maxWidth:500.maxHeight:500.width:600.height:600 })
win.loadURL('https://github.com')
Copy the code

When usingminWidth/maxWidth/minHeight/maxHeightWhen the minimum or maximum window size is set, it only limits the user. It does not prevent you from passing values that do not conform to size limits tosetBounds/setSizeBrowserWindowConstructor of.

6.2.2. Related events

The name of the event The trigger condition
maximize Triggered when the window is maximized
unmaximize Triggered when a window exits from the maximized state
minimize Triggered when a window is minimized
restore Triggered when a window resumes from its minimized state

6.2.3. Related status API

  1. win.minimizableWhether the window can be minimized
  2. win.maximizableWhether the window can be maximized
  3. win.isMaximized()Maximization
  4. win.isMinimized()Minimized or not

6.2.4. Control API

  1. win.maximize()Maximize the window
  2. win.unmaximize()Maximization of exit
  3. win.minimize()Minimize the window
  4. win.unminimize()Exit minimization

6.3. The window is restored

Win.restore () restores the window from its minimized state to its previous state. The main window hiding and recovery in the previous example also applies to this API

7. Triggering sequence of window events

7.1. Window loading

WebContents: webContents object in BrowserWindow instance webPreferences: The webPreferences configuration object for Options in New BrowserWindow(Options)

Work from top to bottom

The environment The event trigger
The preload webPreferences Preload the specified script before the page runs other scripts. This script has access to all Node API scripts regardless of whether the page is integrated with Node. The script path is the absolute path of the file.
webContents did-start-loading This event is emitted when the spinner in the TAB starts to spin
webContents did-start-navigation This event is emitted when the window starts navigation
In the windowJavaScript DOMContentLoaded The initial HTML document is fully loaded and parsed
In the windowJavaScript load When all page resources have been loaded
BrowserWindowThe instance show Window display when triggered
webContents did-frame-navigate frameEnd of navigation
webContents did-navigate main frameEnd of navigation
BrowserWindowThe instance page-title-updated Triggered when the title of a document changes
webContents page-title-updated Triggered when the title of a document changes
webContents dom-ready This event is triggered when text in a frame has finished loading
webContents did-frame-finish-load Triggered when the frame completes navigation
webContents did-finish-load Triggered when navigation is complete, that is, the TAB’s spinner will stop spinning
webContents did-stop-loading This event is emitted when the spinner in the TAB ends its rotation

7.2. User triggers event after loading window (excluding REsize and Move)

The event role
page-title-updated Triggered when the title of a document changes
blur Triggered when a window loses focus
focus Triggered when the window gets focus
hide Window hidden
show Window display
maximize Triggered when the window is maximized (MAC is double click title)
unmaximize Triggered when a window exits from the maximized state
enter-full-screen Triggered when the window enters the full-screen state
leave-full-screen Triggered when the window leaves the full screen state
enter-html-full-screen Triggered when the window enters the full-screen state triggered by the HTML API
leave-html-full-screen Triggered when the window leaves the full-screen state triggered by the HTML API
always-on-top-changed The Set or unset window always fires when the other window is displayed at the top.
app-command window linuxunique

7.3. User movement window

  1. Before moving the windowwill-move;
  2. Moving windowmove;
  3. After movingmoved;

7.4. The user changes the window size

  1. Before the changewill-resize;
  2. After the changeresize

7.5. Window content exception Events (webContentEvents)

The event name Wrong type
unresponsive Triggered when the page becomes unresponsive
responsive Triggered when an unresponded page becomes responsive
did-fail-load Load failed,Error code
did-fail-provisional-load During page loading, executewindow.stop()
did-frame-finish-load
crashed Triggered when the renderer process crashes or is terminated
render-process-gone Emitted when the render process fails unexpectedly
plugin-crashed Triggered when a plug-in process crashes
certificate-error The link validation of the certificate failed
preload-error preload.jsThrow an error

7.6. Window Closure (including unexpected closure)

  • Before closing: triggers registration in the main processcloseThe event
  • Within the windowJavaScriptperformwindow.onbeforeunload 
  • Within the windowJavaScriptperformwindow.onunload 
  • After closing: triggers registration in the main processclosedThe event

In order to learn electron better, we have created a series at present, if you are interested, you can have a look

  • [Electron playground series] Menu
  • 【Electron Playground series 】Dialog with file selection
  • [Electron playground series] Agreement
  • 【Electron Playground series 】 The tray
  • How long does it take to write a screen recording tool?

For more complete documentation, please refer to the documentation below

Electron-Playground official document

Github address portal: github.com/tal-tech/el…