Why is my ipcMain not sending to ipcRenderer in El

2019-07-25 02:40发布

问题:

New to electron I've figured out how to send from Renderer to Main but I'm trying to learn how to go from Main to Renderer. In my research I've read:

IPC send from main process to renderer and tried:

main.js:

const { app, ipcMain, Menu }  = require('electron')
const appVersion = process.env.npm_package_version
const mainWindow = require('./renderer/mainWindow')

app.on('ready', () => {
  mainWindow.createWindow(),
  console.log(`Trying to send app version to renderer: ${appVersion}`),
  mainWindow.webContents.send('app-version', appVersion),  
  Menu.setApplicationMenu(mainMenu)
})

but I get an error of:

Uncaught Exception TypeError Cannot read property 'send' of undefined

After reading "Send sync message from IpcMain to IpcRenderer - Electron" I tried:

ipcMain.on('app-version', (event) => {
    console.log(`Sent: ${appVersion}`)
    event.sender.send(appVersion)
}),

but nothing happens or errors out. My renderer.js:

const { ipcRenderer } = require('electron')
ipcRenderer.on('app-version', (event, res) => {
    console.log(res)
})

Why is my ipcMain not sending to my ipcRenderer?

Edit:

mainWindow.js:

// Modules
const { BrowserWindow } = require('electron')

// export mainWindow
exports.createWindow = () => {

  // BrowserWindow options
  // https://electronjs.org/docs/api/browser-window#new-browserwindowoptions
  this.win = new BrowserWindow({
      minWidth: 400,
      minHeight: 400,
      frame: false,
      webPreferences: {
        nodeIntegration: true,
        backgroundThrottling: false
      }
  })

  // Devtools
  this.win.webContents.openDevTools()

  // Load main window content
  this.win.loadURL(`file://${__dirname}/index.html`)

  // Handle window closed
  this.win.on('closed', () => {
    this.win = null
  })
}

I've also tried:

main.js:

app.on('ready', () => {
  mainWindow.createWindow(),
  mainWindow.win.webContents.send('app-version', appVersion),  
  Menu.setApplicationMenu(mainMenu)
})

renderer.js:

console.log("Trying")
ipcRenderer.on('app-version', (args) => {
    console.log(`Node version is ${args}`)
})

For some reason now the applied answer I've written ipcMain sends to renderer several times and renders the console message repeatedly

Trying to send app version to renderer: 1.0.0
Trying to send app version to renderer: 1.0.0
Trying to send app version to renderer: 1.0.0
Trying to send app version to renderer: 1.0.0
Trying to send app version to renderer: 1.0.0

Now the application flashes but in the console.log it shows it once and I do not understand why.

Edit

To respond to the answer.

I'm aware of app.getVersion I was just using ENV to learn how to send to main. After testing the code this approach doesn't work.

Coping this:

app.on('ready', () => {
    const win = mainWindow.createWindow(),
    console.log(`Trying to send app version to renderer: ${appVersion}`),
    win.webContents.send('app-version', appVersion),  
    Menu.setApplicationMenu(mainMenu)
})

throws the error of:

Missing initializer in const declaration

so modified to this:

app.on('ready', () => {
  const win = mainWindow.createWindow()
  console.log(`Trying to send app version to renderer: ${appVersion}`)
  win.webContents.send('app-version', appVersion)
  Menu.setApplicationMenu(mainMenu)
})

but when adding the return at the end of exports.createWindow throws this error of:

win is not defined

adding let.win before exports.createWindow will not throw a code but nothing is sent over.


Electron Fiddle

index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Electron learning</title>
    <!-- CSS Bootstrap -->
    <!-- <link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.css"> -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <!-- 
      Font Awesome 
      https://fontawesome.com/v4.7.0/cheatsheet/ 
    -->
    <!-- <link rel="stylesheet" href="../node_modules/font-awesome/css/font-awesome.css"> -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

    <!-- Custom CSS -->
    <style>
      body {
        -webkit-app-region: drag;
      }
      footer {
        position: fixed;
        bottom: 0;
        width: 100%;
      }
      #close_app,
      #site {
        cursor: pointer;
      }
    </style>
  </head>
  <body class="d-flex flex-column h-100">
    <header>
      <nav class="navbar navbar-expand navbar-dark fixed-top bg-dark">
        <a class="navbar-brand text-white">Foobar</a>
        <div class="collapse navbar-collapse justify-content-between">
          <div class="navbar-nav">
            <a id="site" class="nav-link"><small><u id="application"></u></small></a>
          </div>
          <div class="navbar-nav">
              <a id="close_app" class="nav-item nav-link"><i class="fa fa-times-circle"></i></a>
          </div>
        </div>
      </nav>
    </header>

    <main role="main" class="flex-shrink-0">
    <div class="container">
      <h1>Hello World!</h1>
      <!-- All of the Node.js APIs are available in this renderer process. -->
      We are using Node.js <script>document.write(process.versions.node)</script>,
      Chromium <script>document.write(process.versions.chrome)</script>,
      and Electron <script>document.write(process.versions.electron)</script>.
      Electron app version <script>document.write(process.versions.electron)</script>.
    </div>
    <p id="testSender"></p>
    </main>

    <footer class="footer mt-auto py-3 ">
    <div class="container">
      <span class="text-muted">Place sticky footer content here.</span>
    </div>
  </footer>
    <script>
        // jQuery
        // window.jQuery = window.$ = $ = require('jquery')

        // You can also require other files to run in this process
        require('./renderer.js')
    </script>
    <!-- <script src="../node_modules/jquery/dist/jquery.min.js"></script> -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>


    <!-- <script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script> -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
  </body>
</html>

main.js:

'use strict'

// Modules to control application life and create native browser window
const { app, ipcMain, BrowserWindow, Menu }  = require('electron')
const testMainSend = `Trying to send something to renderer`

let mainWindow

// Window state keeper
const windowStateKeeper = require('electron-window-state')

// export mainWindow
function createWindow () {

  let winState = windowStateKeeper({
    defaultWidth: 400,
    defaultHeight: 400
  })

  // BrowserWindow options
  // https://electronjs.org/docs/api/browser-window#new-browserwindowoptions
  const win = new BrowserWindow({
    width: winState.width,
    height: winState.Height,
    x: winState.x,
    y: winState.y,
    minWidth: 400,
    minHeight: 400,
    frame: false,
    webPreferences: {
      nodeIntegration: true,
      backgroundThrottling: false
    }
  })

  winState.manage(win)

  // Devtools
  win.webContents.openDevTools()

  // Load main window content
  win.loadURL(`file://${__dirname}/index.html`)

  // Handle window closed
  win.on('closed', () => {
      this.win = null
  })

  return win
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', () => {
  createWindow()

  // 1st attempt
  // webContents.send('misc-sender', testMainSend)

  // 2nd attempt
  // const win = createWindow()
  // win.webContents.send('misc-sender', testMainSend)

  // 3rd attempt
  const win = createWindow()
  win.webContents.on('dom-ready', () => {
    console.log(`Trying to send renderer: ${testMainSend}`)
    mainWindow.win.webContents.send('misc-sender', testMainSend)
  })
})

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow()
  }
})

// Close application from button
ipcMain.on('closing-app', () => {
  app.quit()
  console.log('Closed app from font awesome link')
})

renderer.js:

// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.
const { ipcRenderer, shell } = require('electron')
const appVersion = require('electron').remote.app.getVersion()

// Devtron
// require('devtron').install()

// Close App
const closeApp = document.getElementById('close_app')
closeApp.addEventListener('click', () => {
    ipcRenderer.send('closing-app')
})

// received from ipcMain test
ipcRenderer.on('misc-sender', (event, args) => {
    appendTest = document.getElementById('testSender')
    appendTest.innerHTML += args
})

// Getting version
const appVersioning = document.getElementById('application')
appVersioning.innerHTML = appVersion

// Open site
const homeURL = document.getElementById('site')
homeURL.addEventListener('click', (e) => {
    e.preventDefault
    shell.openExternal("https://www.google.com/")
})

回答1:

After several searches and attempts I think I've finally figured out how to send my application version from package.json to main then to the renderer. My issue was in my app.on I was missing dom-ready which helped after reading IPC Communication not working between Electron and window:

main.js:

const appVersion = process.env.npm_package_version

app.on('ready', () => {
  mainWindow.createWindow()
  Menu.setApplicationMenu(mainMenu)

  // Send version to renderer
  mainWindow.win.webContents.on('dom-ready', () => {
    console.log(`Trying to send app version to renderer: ${appVersion}`)
    mainWindow.win.webContents.send('app-version', appVersion)
  })
})

renderer.js:

ipcRenderer.on('app-version', (event, args) => {
    const appVersion = document.getElementById('app_version')
    console.log(`Node version is ${args}`)
    appVersion.innerHTML += args
})

index.html:

<div id="app_version"></div>

There might be a better way to do this but after further research I read:

  • Electron - How to know when renderer window is ready
  • dom-ready from instance-events

and this works but next steps are to see if pulling the process.env is a good security practice. I do hope to see some other answers on a possible better approach if it exists.



回答2:

Before answering your question, looking at the message you are sending to the renderer ( if what you want to do is send your application version to the render process ) it can be done with app.getVersion. From your main process you have to set the app version with app.setVersion("1.0") and then from your render process you have to do this

const { remote: { app } } = require("electron");
app.getVersion();

To your actual question mainWindow.createWindow() needs to return an instance of BrowserWindow ( looking at your code you are not returning it and also you are not reading the win property object you set on mainWindow.js ). If you want to stick with the current code in your question you have to do this

mainWindow.win.webContents.send(...)

or you should do this from

mainWindow.js

// Modules
const { BrowserWindow } = require('electron')

// export mainWindow
exports.createWindow = () => {

    // BrowserWindow options
    // https://electronjs.org/docs/api/browser-window#new-browserwindowoptions
    const win = new BrowserWindow({
        minWidth: 400,
        minHeight: 400,
        frame: false,
        webPreferences: {
            nodeIntegration: true,
            backgroundThrottling: false
        }
    })

    // Devtools
    win.webContents.openDevTools()

    // Load main window content
    win.loadURL(`file://${__dirname}/index.html`)

    // Handle window closed
    win.on('closed', () => {
        this.win = null
    })
    return win;
}

main.js

const { app, ipcMain, Menu }  = require('electron')
const appVersion = process.env.npm_package_version
const mainWindow = require('./renderer/mainWindow')

app.on('ready', () => {
    const win = mainWindow.createWindow(),
    console.log(`Trying to send app version to renderer: ${appVersion}`),
    win.webContents.send('app-version', appVersion),  
    Menu.setApplicationMenu(mainMenu)
})