Ricerca nel sito web

javascript - Perché il mio ipcMain non invia a ipcRenderer in Electron?


Nuovo in Electron, ho capito come inviare da Renderer a Main, ma sto cercando di imparare come andare da Main a Renderer. Nelle mie ricerche ho letto:

IPC inviato dal processo principale al renderer e provato:

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)
})

ma ricevo un errore di:

Eccezione non catturata TypeError Impossibile leggere la proprietà 'send' di undefined

Dopo aver letto "Inviare un messaggio sincrono da IpcMain a IpcRenderer - Electron" ho provato:

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

ma non succede nulla o si verifica un errore. Il mio renderer.js:

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

Perché il mio ipcMain non invia al mio ipcRenderer?

Modifica :

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
  })
}

Ho anche provato:

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}`)
})

Per qualche motivo ora la risposta applicata che ho scritto ipcMain invia al renderer più volte e stampa ripetutamente il messaggio sulla console

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

Ora l'applicazione lampeggia ma nel console.log viene mostrata una volta e non capisco perché.

Modifica

Rispondere alla risposta.

Sono a conoscenza di app.getVersion, stavo solo usando ENV per imparare come inviare al main. Dopo aver testato il codice, questo approccio non funziona.

Copiare questo:

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)
})

genera l'errore di:

Inizializzatore mancante nella dichiarazione const

quindi modificato in questo modo:

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)
})

ma quando si aggiunge il return alla fine di exports.createWindow viene generato questo errore:

win non è definito

aggiungere let.win prima di exports.createWindow non genererà un errore ma nulla viene inviato.

Violino Elettronico

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></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/")
})

Risposta n. 1

Dopo diverse ricerche e tentativi, penso di aver finalmente capito come inviare la versione della mia applicazione da package.json al main e poi al renderer. Il mio problema era nel mio app.on, mancava dom-ready che ha aiutato dopo aver letto Che la comunicazione IPC non funziona tra Electron e la finestra:

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>

Potrebbe esserci un modo migliore per fare questo, ma dopo ulteriori ricerche ho letto:

  • Electron - Come sapere quando la finestra del renderer è pronta
  • dom-ready da instance-events

e questo funziona, ma i prossimi passi sono vedere se estrarre il process.env è una buona pratica di sicurezza. Spero davvero di vedere altre risposte su un possibile approccio migliore se esiste.

Risposta n. 2

Prima di rispondere alla tua domanda, guardando il messaggio che stai inviando al renderer (se quello che vuoi fare è inviare la versione della tua applicazione al processo di rendering) può essere fatto con app.getVersion. Dal tuo processo principale devi impostare la versione dell'app con app.setVersion("1.0") e poi dal tuo processo di rendering devi fare questo

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

Alla tua effettiva domanda mainWindow.createWindow() deve restituire un'istanza di BrowserWindow (guardando il tuo codice, non la stai restituendo e inoltre non stai leggendo l'oggetto proprietà win che hai impostato su mainWindow.js). Se vuoi attenerti al codice attuale nella tua domanda devi fare questo

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

o dovresti fare questo da

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)
})