Shiv ENOW large front end

Company official website: CVTE(Guangzhou Shiyuan Stock)

Team: ENOW team of CVTE software Platform Center for Future Education

The author:

preface

You wish the world, I wish you no bugs. Hello everyone! I’m Lin Dull.

In the previous chapter, we introduced how the main process and the renderer process communicate with each other. What about renderer to renderer? How do they send messages to each other? Let’s take a look.

This series consists of the following chapters:

  • The main process and the renderer process love each other
  • The renderer connects to the renderer process
  • A telephonist of loveport

This time you are reading chapter 1: The love affair between the main process and the renderer process.

Note: All of the above articles are filed to: github.com/LinDaiDai/n… , cases are uploaded to: github.com/LinDaiDai/e… Welcome Star, thank you Star.

Case Version Information:

  • Electron: v13.6.7
  • Nodejs: v16.13.2

The outline

In fact, communicating with renderers can be a lot more difficult.

First we know that in a Electron project, there can only be one main process and multiple renderers. So if you want to send a message between renderers, you need to know which renderer you want to send a message to. There is a concept like id, otherwise you can’t tell which one is which.

Second, where do I get the ID of each render process? Global variables? The main process?

OKK, I made it sound complicated. Before we panic, let’s take a look at the methods that the IPC module can use to enable the renderer to communicate with the renderer:

  • ipcRenderer.sendTo(webContentsId, channel, ... args)Use:ipcRendererTo provide thesendToMethod to specify which renderer process (webContentsId) send a message;
  • window.webContents.sendThe main process holds all rendererswebContentsObject, and the main process has the ability to receive messages from the renderer process, so the main process can act as a middleman, so that the communication between the renderer processes can be;

1. IpcRender sendTo case

Before we formally introduce this method of communication, let’s define what to do:

1. Expect the main process to provide the following capabilities:

  • The ability to create Windows;
  • Creates a window that can be added to a new windowidStore it down and on these WindowsidMaintenance;
  • Provides a window for the renderer process to retrieveidThe ability to

2. Create and open two Windows (rendering process) for easy debugging

Send a message from window 1 to window 2 via sendTo

The whole sequence process is shown in the figure below:

(wow! You’re really good! You do sequence diagrams! . Pa!)

Okay, now that we know what we’re gonna do, let’s do it. After this example, you should understand how sendTo is used.

Step 1: Adjust the directory structure

Since this section deals with the renderer process communicating with the renderer process, let’s create two Windows (window-one and window-two) and adjust the project directory to:

Since the main process is placed in SRC /main/index.js, the package.json entry should also be modified:

{
- "main": "src/main.js",
+ "main": "src/main/index.js",
}
Copy the code

Second, the main process provides the create window as well as the store windowidThe ability to

With that said, let’s first implement the code logic for the main process. To make the responsibilities of each section clearer, we define two more files to handle their respective contents:

Main.js Main process logic code;

Createwindow.js encapsulates the public method for creating a window;

Windowmanager.js ability to maintain all window ids.

OKK, I’m sure you’ll agree with me that creating different Windows is a bit of repetitive logic, so let’s encapsulate it:

CreateWindow. Js:

const { BrowserWindow } = require('electron')

exports.createWindow = function (params) {
  const { name, width, height, loadFileUrl } = params;
  const window = new BrowserWindow({
    name,
    width,
    height,
    webPreferences: {
      nodeIntegration: true.contextIsolation: false,}})window.loadFile(loadFileUrl)

  window.webContents.on('did-finish-load'.() = > {
    // do something
  })

  window.webContents.on('destroyed'.() = > {
    // do something})}Copy the code

The window name, size, path, and so on are all passed in with createWindow parameters.

Then, as windowManager.js does, we’ll simply write the code:

const { ipcMain } = require('electron')

// Store all window ids
const windowIdMap = {}

// Register window
exports.registerWindowId = function(key, value) {
    windowIdMap[key] = value;
    console.log('registerWindowId', windowIdMap);
}

// Destroy the window
exports.removeWindowId = function(key) {
    delete windowIdMap[key];
    console.log('removeWindowId', windowIdMap);
}

// Get a window ID
ipcMain.on('getWindowId'.(event, arg) = > {
    console.log('getWindowId', arg);
    event.returnValue = windowIdMap[arg];
})
Copy the code

According to its responsibilities, we defined a minimal version of windowManager: three functions to register, destroy, and get the specified window ID.

The registration and destruction methods are easy to understand, but simply add changes to the windowIdMap object.

Get the specified window ID. Because getting the id of a window is usually something the renderer does, I’m using ipcmain. on to listen for the “getWindowId” event, The renderer simply sends a message to the main process using the ipcrenderer. sendSync method mentioned above.

Now let’s combine windowManager with createWindow: I expect the window ID to be automatically registered with windowManager when a window is created; When a window is destroyed, it also destroys the window ID in windowManager.

Createwindow.js createWindow.js

const { BrowserWindow } = require('electron')
// Introduce windowManager
const { registerWindowId, removeWindowId } = require('./windowManager');

exports.createWindow = function (params) {
  const { name, width, height, loadFileUrl } = params;
  const window = new BrowserWindow({
    name,
    width,
    height,
    webPreferences: {
      nodeIntegration: true.contextIsolation: false,}})window.loadFile(loadFileUrl)

  window.webContents.on('did-finish-load'.() = > {
    // Register window id
    registerWindowId(name, window.webContents.id);
  })

  window.webContents.on('destroyed'.() = > {
    // Destroy window idremoveWindowId(name); })}Copy the code

Here I’m listening for the did-final-load event for each window, calling the method to register the window in the event callback with the window name as the key and window.webcontents.id as the value.

The renderer needs to know window.webcontents.id when sending a message to the specified window via sendTo.

Ha ha ha, have the above steps, create Windows and window id registered it becomes intelligent, let us then to look down.

Third, write the code logic for the main process to create two Windows

The generic code is already wrapped, so let’s apply it to the main process:

const { app, BrowserWindow } = require('electron')
const { createWindow } = require('./createWindow');

function createWindowOne() {
  createWindow({
    name: 'one'.width: 1000.height: 800.loadFileUrl: 'src/windows/window-one/index.html'})},function createWindowTwo() {
  createWindow({
    name: 'two'.width: 800.height: 600.loadFileUrl: 'src/windows/window-two/index.html',
  })
}

app.whenReady().then(() = > {
  createWindowOne();
  createWindowTwo();
});

// Omit app listening for 'window-all-closed' and 'activate' logic below
Copy the code

When the project is started, create and open two render process Windows, window-one and window-two, by calling createWindow and passing in the corresponding parameters.

Now, the windowIdMap in Windows Manager should be changed to:

{
  'one': 1.'two': 2,}Copy the code

If the renderer calls ipcrenderer.sendsync (‘getWindowId’, ‘two’), it should get: 2. OKK is what we expected.

Step 4: Write the renderer code logic

With the main process code done, the renderer side of the process is simple.

We simply write the HTML for window-One and window-two, and then implement window-One to send messages to Window-Two.

The window – one/the renderer. Js:

const { ipcRenderer } = require('electron');

function sendToWindowTwo() {
    const windowOneId = ipcRenderer.sendSync('getWindowId'.'two');
    console.log('windowOneId', windowOneId);
    ipcRenderer.sendTo(windowOneId, 'windowOne-send-to-windowTwo'.'Window 1 sends a message to window 2 via sendTo');
}
Copy the code

The window – one. HTML:

<body>
    <h1>Window One</h1>
    <button onclick="sendToWindowTwo()">Window 1 sends a message to window 2 via sendTo</button>
    <script src="./renderer.js"></script>
</body>
Copy the code

In window 1, when the button is clicked, the sendToWindowTwo method is called, which first gets the window ID of window-Two from the main process via sendSync. Send a message to Window-Two via ipcrenderer. sendTo.

For window two:

The window – two/the renderer. Js:

const { ipcRenderer } = require('electron');

ipcRenderer.on('windowOne-send-to-windowTwo'.(event, arg) = > {
    console.log('receive:', arg);
})
Copy the code

The window – two HTML:

<body>
    <h1>Window Two</h1>
    <script src="./renderer.js"></script>
</body>
Copy the code

Window 2 simply listens for Windowone-send-to-WindowTwo events and receives the return value.

OKK has done all of this, so let’s see what happens:

As shown in the figure above, click the button of window 1 to successfully send a message to window 2.

As you can see, this method is dependent on the main process, because the renderer needs to fetch the id of another renderer from it.

You can do this in other ways, such as storing the id mapping relationship in a global variable and fetching it from it each time, but be careful to update the mapping because the renderer creates and destroys different ids. The benefit, however, is that each communication reduces the number of message requests sent to the main process. Of course, if you are interested, you can try it yourself. This article will not be expanded.

2. Window. WebContents. Send case

We’ve already seen how to communicate between renderers using ipcrenderer.sendto, so let’s look at the second method, window.webcontents.send.

This method also relies on the main process, and is more dependent than the first method…

The general implementation is as follows:

Every time a new renderer is created, the renderer’s window.webContents object is saved in the main process.

When the renderer wants to send a message to the second renderer, it informs the main process, along with the event name (message name) and parameters (message content);

3. The main process finds renderer 2’s window.webContents and sends a message to renderer 2 via its send method.

Render process 2 uses ipcrenderer. on to receive.

With that in mind, let’s take a look at what each part should be capable of:

1. Expect the main process to provide the following capabilities:

  • The ability to create Windows;
  • Creates a window that can be added to a new windowwindow.webContentsStore it down and on these Windowswindow.webContentsMaintenance;
  • The ability to notify renderers to send messages to other renderers

2. The renderer process needs to provide the ability to send messages to the main process as well as the ability to receive messages.

Ok, so we’ve defined the implementation and the responsibilities of each part, so let’s do it again.

First, the main process provides storagewindow.webContentsThe ability to

Let’s take a look at the main process, which creates Windows and stores window.webcontens. We can use the example above to implement this, leaving the project directory unchanged:

The windowManager.js script needs to be modified to store the id of each window, but now it needs to store its webContents:

Js script is so generic that you don’t need to rename it. If I chose windowIdManager instead of ID, in this case I’d probably change it to windowContentsManager.

// windowManager.js
const { ipcMain } = require('electron')

const windowContentsMap = {}

exports.registerWindowContents = function(key, value) {
    windowContentsMap[key] = value;
    console.log('registerWindowContents', windowContentsMap);
}

exports.removeWindowContents = function(key) {
    delete windowContentsMap[key];
    console.log('removeWindowContents', windowContentsMap);
}

function getWindowContents(key) {
    return windowContentsMap[key];
}

/** * event: event object * params: {channel: string; targetWindow: string; Data: any} * channel: event name; TargetWindow: unique identifier of the targetWindow; Data: The content to be passed */
ipcMain.on('renderer-send-to-renderer'.(event, params) = > {
    console.log('renderer-send-to-renderer', params);
    const { channel, targetWindow, data } = params;
    const contents = getWindowContents(targetWindow);
    if (contents) {
        contents.send(channel, data);
    } else {
        console.error('targetWindow non-existent'); }})Copy the code

The code above is also very simple, we have changed windowIdMap to windowContentsMap, which stores windowContents objects.

IpcMain also listens for an event called renderer-send-to-renderer, in which we send a message to the target window.

Second, change the logic of creating the window

Createwindow.js (createWindow.js, createWindow.js, createWindow.js, createWindow.js)

const { BrowserWindow } = require('electron')
// Introduce windowManager
const { registerWindowContents, removeWindowContents } = require('./windowManager');

/** * Public window creation method *@param {*} Params parameters * /
exports.createWindow = function (params) {
  const { name, width, height, loadFileUrl } = params;
  const window = new BrowserWindow({
    name,
    width,
    height,
    webPreferences: {
      nodeIntegration: true.contextIsolation: false,}})window.loadFile(loadFileUrl)

  // Register webContents
  window.webContents.on('did-finish-load'.() = > {
    registerWindowContents(name, window.webContents);
  })
  // Destroy webContents
  window.webContents.on('destroyed'.() = >{ removeWindowContents(name); })}Copy the code

When you register, instead of passing in the ID, you pass in window.webcontents.

The third step is to implement the logic for the renderer to send and receive messages

Now that the main process is implemented, the render process is also easy to handle.

The window – one/the renderer. Js:

const { ipcRenderer } = require('electron');

function sendToWindowTwo() {
    ipcRenderer.send('renderer-send-to-renderer', {
        channel: 'windowOne-send-to-windowTwo'.targetWindow: 'two'.data: 'Window 1 sends a message to window 2 via sendTo'}); }Copy the code

This time, we just need to send a renderer-send-to-renderer message to the main process and pass the rest in parameters. For example, if I want to notify windowTwo that you want to execute a windowone-send-to-windowtwo event, put the event name in a channel.

For window 2, we need to listen for windowone-send-to-WindowTwo events:

const { ipcRenderer } = require('electron');

ipcRenderer.on('windowOne-send-to-windowTwo'.(event, arg) = > {
    console.log('event:', event);
    console.log('receive:', arg);
})
Copy the code

At this point, let’s take a look at the effect, which is what we expected.

GOOD~

conclusion

OKK, having introduced the features and analyzed the case, let’s summarize.

Render process prior to communication:

  • ipcRenderer.sendTo(webContentsId, channel, ... args)Use:ipcRendererTo provide thesendToMethod to specify which renderer process (webContentsId) send a message;
  • window.webContents.sendThe main process holds all rendererswebContentsObject, and the main process has the ability to receive messages from the renderer process, so the main process can act as a middleman, so that the communication between the renderer processes can be;

After the language

So much for this article. Through the introduction of the first two chapters, I believe you have a general understanding of ipc communication. In the next chapter, I will introduce you to the telephonist port. Let’s call it a day. Bye!

Like “Lin dull” guy also hope to pay attention to Lin dull public number LinDaiDai.

I will update some front-end knowledge content and my original article 🎉 from time to time

Your encouragement is the main motivation for my continuous creation 😊.