当前位置:网站首页>Introduction to electron tutorial 3 - process communication

Introduction to electron tutorial 3 - process communication

2022-04-23 19:46:00 Harm the evil king

Welcome to Electron The third tutorial of the introductory tutorial , This section is very important ! Interprocess communication (IPC) Is in Electron A key part of building feature rich desktop applications in . Because the main process and rendering process are Electron There are different responsibilities in the process model ,IPC Is the only way to perform many common tasks , For instance from UI Call local API Or trigger from local menu web Changes in content . Let's introduce in detail 3 A common way of communication .

* One way communication between rendering process and main process

stay Electron in , The process is defined by the developer “ passageway ” And ipcMain Module and ipcRenderer Module to communicate . These channels are Any of the ( You can name them as you like ) and Bidirectional ( You can use the same channel name for both modules ). To send a one-way message from the rendering process to the main process IPC news , You can pre render the script again preload.js Use in ipcRenderer send out API Send a message , And then in main.js In the use ipcMain.on receive . You usually use this pattern from your web Call a middle note in the content API. We will demonstrate this pattern by creating a simple application , The application can programmatically change the title of the window .

Let's use code to demonstrate this process , Here are all the codes of the case :

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> Process of communication </title>
</head>
<body>
Title: <input id="title"/>
<button id="btn" type="button">Set</button>
<script src="./index.js"></script>
</body>
</html>

preload.js

const {
     contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    
    setTitle: (title) => ipcRenderer.send('set-title', title)
})

index.js

const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    
    const title = titleInput.value
    window.electronAPI.setTitle(title)
});

main.js

const {
    app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')

function createWindow () {
    
    const mainWindow = new BrowserWindow({
    
        webPreferences: {
    
            preload: path.join(__dirname, 'preload.js')
        }
    })

    ipcMain.on('set-title', (event, title) => {
    
        const webContents = event.sender
        const win = BrowserWindow.fromWebContents(webContents)
        win.setTitle(title)
    })

    mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
    
    createWindow()

    app.on('activate', function () {
    
        if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
})

app.on('window-all-closed', function () {
    
    if (process.platform !== 'darwin') app.quit()
})

The operation effect is as follows (GIF It's a little slow , Don't you mind? ):
 Insert picture description here
Here are some key points of the code :
1. Listen for events in the main process
In the main process , We use ipcMain stay set-title Set a... On the channel IPC Monitor , This set-title We are pre rendering the script preload.js The interface channel defined inside .

    ipcMain.on('set-title', (event, title) => {
    
        const webContents = event.sender
        const win = BrowserWindow.fromWebContents(webContents)
        win.setTitle(title)
    })

Whenever a message passes set-title Channel incoming , This function will find the attached to the message sender BrowserWindow example , And use win.setTitle Set the title of the application window .

2. Define the interface channel in the preload script
To send a message to the listener created above , You can use ipcRenderer. send out API. By default , The renderer process did not Node.js or Electron Module access . As an application developer , You need to use contextBridge Select which from the preloaded scripts to expose API. here , You will be able to use window.electronAPI.setTitle() function .

const {
     contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    
    setTitle: (title) => ipcRenderer.send('set-title', title)
})

* Two way communication between rendering process and main process

two-way IPC A common application is to invoke the middle note module from the rendering process code and wait for the result. . This can be done by using ipcRenderer.invoke To achieve , call ipcMain.handle pairing . In the following example , We will open a select local file dialog from the rendering process , And return the path of the selected file .

Here are all the codes involved in the case :

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> Process of communication </title>
</head>
<body>
<button type="button" id="btn">Open a File</button>
File path: <strong id="filePath"></strong>
<script src='./index.js'></script>
</body>
</html>

index.js

const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => {
    
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})

main.js

const {
    app, BrowserWindow, ipcMain,dialog} = require('electron')
const path = require('path')

async function handleFileOpen() {
    
  const {
     canceled, filePaths } = await dialog.showOpenDialog()
  if (canceled) {
    
    return ""
  } else {
    
    return filePaths[0]
  }
}

function createWindow () {
    
  const mainWindow = new BrowserWindow({
    
    webPreferences: {
    
      preload: path.join(__dirname, 'preload.js')
    }
  })
  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
    
  ipcMain.handle('openFileDialog', handleFileOpen)
  createWindow()
  app.on('activate', function () {
    
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
    
  if (process.platform !== 'darwin') app.quit()
})

preload.js

const {
     contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI',{
    
  openFile: () => ipcRenderer.invoke('openFileDialog')
})

Running effect demonstration :
 Insert picture description here
Here are some key points of the code :
1. Define the event handler function in the main process , And monitor ICP Interface call
In the main process , We will create a call dialog Modular showOpenDialog Function of method handleFileOpen(), The value used to return the file path selected by the user . After the application is ready , It calls ipcMain.handle() To monitor the rendering process ipcRenderer.invoke('openFileDialog') The definition of openFileDialog. When index.js It calls window.electronAPI.openFile() when , Will trigger openFileDialog, After being monitored and processed by the main process , Return results .

2. Call to define the interface through the preloaded script
In the preload script , We opened a single line openFile function , It calls and returns ipcRederer .invoke('openFileDialog').
stay index.js In the code snippet , We're listening to #btn Button click , And call window.electronAPI.openFile() To activate local openFile Dialog box . And then in #filePath Element displays the selected file path .

3. ipcRenderer.invoke An alternative
ipcRenderer.invoke() There are two alternatives :
(1)ipcRenderer.send() : The one-way communication we use can also be used to perform two-way communication . This is Electron 7 Passed before IPC Recommended method for asynchronous two-way communication .

preload.js

const {
     ipcRenderer } = require('electron')

ipcRenderer.on('asynchronous-reply', (_event, arg) => {
    
  console.log(arg)
  //  prints pong
})
ipcRenderer.send('asynchronous-message', 'ping')

main,js

ipcMain.on('asynchronous-message', (event, arg) => {
    
  console.log(arg)
  // I will promise ping
  event.reply('asynchronous-reply', 'pong')
})

(1) ipcRenderer.sendSync() : This method sends a message to the main process , And wait for the response synchronously .

preload.js

const {
     ipcRenderer } = require('electron')

const result = ipcRenderer.sendSync('synchronous-message', 'ping')
console.log(result)
//  prints pong

main.js

const {
     ipcMain } = require('electron')
ipcMain.on('synchronous-message', (event, arg) => {
    
  console.log(arg)
  //  prints ping
  event.returnValue = 'pong'
})

The structure of this code is very similar to the call model , But for performance reasons , We recommend avoiding this API. Its synchronous nature means that it will block the renderer process , Until a reply is received .

* One way communication from the main process to the rendering process

When a message is sent from the main process to the rendering process , You need to specify which renderer is receiving the message . The message needs to pass through the main process WebContents The instance is sent to the rendering process . This WebContents The instance contains a sent Method , Can be like ipcReender .send Use it like that . To demonstrate this communication mode , A digital counter controlled by the menu bar will be built .

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> Process of communication </title>
</head>
<body>
Current value: <strong id="counter">0</strong>
<script src='./index.js'></script>
</body>
</html>

preload.js

const {
     contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    
  handleCounter: (callback) => ipcRenderer.on('update-counter', callback)
})

index.js

const counter = document.getElementById('counter')

window.electronAPI.handleCounter((event, value) => {
    
    const oldValue = Number(counter.innerText)
    const newValue = oldValue + value
    counter.innerText = newValue
    event.sender.send('counter-value', newValue)
})

main.js

const {
    app, BrowserWindow, Menu, ipcMain} = require('electron')
const path = require('path')

function createWindow () {
    
  const mainWindow = new BrowserWindow({
    
    webPreferences: {
    
      preload: path.join(__dirname, 'preload.js')
    }
  })

  const menu = Menu.buildFromTemplate([
    {
    
      label: app.name,
      submenu: [
      {
    
        click: () => mainWindow.webContents.send('update-counter', 1),
        label: 'Increment',
      },
      {
    
        click: () => mainWindow.webContents.send('update-counter', -1),
        label: 'Decrement',
      }
      ]
    }

  ])

  Menu.setApplicationMenu(menu)
  mainWindow.loadFile('index.html')

  // Open the DevTools.
  mainWindow.webContents.openDevTools()
}

app.whenReady().then(() => {
    
  ipcMain.on('counter-value', (_event, value) => {
    
    console.log(value) // will print value to Node console
  })
  createWindow()
  
  app.on('activate', function () {
    
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
    
  if (process.platform !== 'darwin') app.quit()
})

Running effect demonstration :
 Insert picture description here
Explain part of the code :

We first need to use it in the main process Electron Of Menu The module builds a custom menu , Send... From the main process to the target renderer IPC news . Click the handler to send a message to the renderer process through the counter channel (1 or -1).

const menu = Menu.buildFromTemplate([
    {
    
      label: app.name,
      submenu: [
        {
    
          click: () => mainWindow.webContents.send('update-counter', 1),
          label: 'Increment',
        },
        {
    
          click: () => mainWindow.webContents.send('update-counter', -1),
          label: 'Decrement',
        }
      ]
    }
  ])
  Menu.setApplicationMenu(menu)

Just like the main process to the previous example , We are preloading the script preload.js Use in contextBridge and ipcRederer The module exposes to the rendering process IPC function :

const {
     contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    
    handleCounter: (callback) => ipcRenderer.on('update-counter', callback)
})

* Communication between rendering processes

stay Electron in , There is no direct method to use between rendering processes ipcMain and ipRenderer Module sends messages , And this communication method is actually very rarely used . Do that , You can use the main process as a message broker between renderers . This will involve sending messages from a renderer to the main process , The main process forwards the message to another renderer , I'm not going to do the demo here .

版权声明
本文为[Harm the evil king]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204231940533385.html