Electron - 快速指南



Electron - 概述

为什么选择 Electron?

Electron 通过提供具有丰富的原生(操作系统)API 的运行时,使您可以使用纯 JavaScript 创建桌面应用程序。

这并不意味着 Electron 是图形用户界面 (GUI) 库的 JavaScript 绑定。相反,Electron 使用网页作为其 GUI,因此您也可以将其视为受 JavaScript 控制的最小 Chromium 浏览器。因此,所有 Electron 应用程序从技术上讲都是在浏览器中运行的网页,可以利用您的操作系统 API。

谁在使用 Electron?

Github 开发了 Electron 用于创建文本编辑器 Atom。它们都于 2014 年开源。Electron 被许多公司使用,例如 Microsoft、Github、Slack 等。

Electron 已被用于创建许多应用程序。以下是一些著名的应用程序:

  • Slack 桌面版
  • Wordpress 桌面应用程序
  • Visual Studio Code
  • Caret Markdown 编辑器
  • Nylas 邮件应用程序
  • GitKraken git 客户端

Electron - 安装

要开始使用 Electron 进行开发,您需要安装 Node 和 npm(节点包管理器)。如果您尚未安装这些,请访问 Node 设置 以在您的本地系统上安装 Node。通过在您的终端中运行以下命令来确认 Node 和 npm 是否已安装。

node --version
npm --version

以上命令将生成以下输出:

v6.9.1
3.10.8

每当我们使用 npm 创建项目时,都需要提供一个package.json文件,其中包含我们项目的所有详细信息。npm 使我们能够轻松设置此文件。让我们设置我们的开发项目。

  • 启动您的终端/cmd,创建一个名为 hello-world 的新文件夹,并使用 cd 命令打开该文件夹。

  • 现在,要使用 npm 创建 package.json 文件,请使用以下命令。

npm init
  • 它会询问您以下信息:

Package.json creation

只需一直按 Enter 键,并在“作者姓名”字段中输入您的姓名。

创建一个新文件夹,并使用 cd 命令打开它。现在运行以下命令以全局安装 Electron。

$ npm install -g electron-prebuilt

执行完毕后,您可以通过运行以下命令检查 Electron 是否已正确安装:

$ electron --version

您应该获得以下输出:

v1.4.13

现在我们已经设置了 Electron,让我们继续使用它创建我们的第一个应用程序。

Electron 是如何工作的

Electron 获取在您的package.json文件中定义的主文件并执行它。此主文件创建应用程序窗口,其中包含呈现的网页以及与操作系统的原生 GUI(图形用户界面)的交互。

当您使用 Electron 启动应用程序时,会创建一个主进程。此主进程负责与操作系统的原生 GUI 交互。它创建应用程序的 GUI。

仅启动主进程不会为应用程序用户提供任何应用程序窗口。这些窗口由主文件中的主进程使用BrowserWindow模块创建。然后,每个浏览器窗口都运行其自己的渲染器进程。渲染器进程获取一个 HTML 文件,该文件引用通常的 CSS 文件、JavaScript 文件、图像等,并在窗口中呈现它。

主进程可以通过 Electron 中直接可用的模块访问原生 GUI。桌面应用程序可以访问所有 Node 模块,例如用于处理文件的 FileSystem 模块、用于发出 HTTP 请求的请求模块等。

主进程和渲染器进程之间的区别

主进程通过创建BrowserWindow实例来创建网页。每个BrowserWindow实例在其自己的渲染器进程中运行网页。当BrowserWindow实例被销毁时,相应的渲染器进程也会终止。

主进程管理所有网页及其相应的渲染器进程。每个渲染器进程都是隔离的,只关心在其内运行的网页。

Electron - Hello World

我们已经为我们的项目创建了一个package.json文件。现在我们将使用 Electron 创建我们的第一个桌面应用程序。

创建一个名为main.js的新文件。在其中输入以下代码:

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

let win  

function createWindow() { 
   win = new BrowserWindow({width: 800, height: 600}) 
   win.loadURL(url.format ({ 
      pathname: path.join(__dirname, 'index.html'), 
      protocol: 'file:', 
      slashes: true 
   })) 
}  

app.on('ready', createWindow) 

创建另一个文件,这次是一个名为index.html的 HTML 文件。在其中输入以下代码。

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>Hello World!</title>
   </head>
   
   <body>
      <h1>Hello World!</h1>
      We are using node <script>document.write(process.versions.node)</script>,
      Chrome <script>document.write(process.versions.chrome)</script>,
      and Electron <script>document.write(process.versions.electron)</script>.
   </body>
</html>

使用以下命令运行此应用程序:

$ electron ./main.js

将打开一个新窗口。它看起来如下所示:

Electron Hello World

此应用程序是如何工作的?

我们创建了一个主文件和一个 HTML 文件。主文件使用两个模块 - appBrowserWindow。app 模块用于控制应用程序的事件生命周期,而 BrowserWindow 模块用于创建和控制浏览器窗口。

我们定义了一个createWindow函数,在其中我们创建一个新的 BrowserWindow 并将一个 URL 附加到此 BrowserWindow。这是我们在运行应用程序时呈现并显示给我们的 HTML 文件。

我们在 html 文件中使用了原生 Electron 对象 process。此对象扩展自 Node.js process 对象,并包含t=其所有功能,同时还添加了许多其他功能。

Electron - 构建 UI

Electron 应用程序的用户界面使用 HTML、CSS 和 JS 构建。因此,我们也可以在这里利用所有可用的前端 Web 开发工具。您可以使用 Angular、Backbone、React、Bootstrap 和 Foundation 等工具来构建应用程序。

您可以使用 Bower 来管理这些前端依赖项。使用以下命令安装 bower:

$ npm install -g bower

现在您可以使用 bower 获取所有可用的 JS 和 CSS 框架、库、插件等。例如,要获取 Bootstrap 的最新稳定版本,请输入以下命令:

$ bower install bootstrap

这将在bower_components中下载 Bootstrap。现在您可以在您的 HTML 中引用此库。让我们使用这些库创建一个简单的页面。

现在让我们使用 npm 命令安装 jquery:

$ npm install --save jquery

此外,这将在我们的 view.js 文件中需要。我们已经设置了一个 main.js,如下所示:

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

let win

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

app.on('ready', createWindow)

打开您的index.html文件并在其中输入以下代码:

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>Hello World!</title>
      <link rel = "stylesheet" 
         href = "./bower_components/bootstrap/dist/css/bootstrap.min.css" />
   </head>
   
   <body>
      <div class = "container">
         <h1>This page is using Bootstrap and jQuery!</h1>
         <h3 id = "click-counter"></h3>
         <button class = "btn btn-success" id = "countbtn">Click here</button>
         <script src = "./view.js" ></script>
      </div>
   </body>
</html>

创建view.js并在其中输入点击计数器逻辑:

let $ = require('jquery')  // jQuery now loaded and assigned to $
let count = 0
$('#click-counter').text(count.toString())
$('#countbtn').on('click', () => {
   count ++ 
   $('#click-counter').text(count)
}) 

使用以下命令运行应用程序:

$ electron ./main.js

以上命令将生成以下屏幕截图中的输出:

UI

您可以像构建网站一样构建您的原生应用程序。如果您不希望用户受限于精确的窗口大小,您可以利用响应式设计并允许用户以灵活的方式使用您的应用程序。

Electron - 文件处理

文件处理是构建桌面应用程序非常重要的一部分。几乎所有桌面应用程序都与文件交互。

我们将在我们的应用程序中创建一个表单,该表单将作为输入获取姓名和电子邮件地址。此表单将保存到文件中,并将创建一个列表以将其显示为输出。

使用main.js文件中的以下代码设置您的主进程:

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

let win

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

app.on('ready', createWindow)

现在打开index.html文件并在其中输入以下代码:

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>File System</title>
      <link rel = "stylesheet" 
         href = "./bower_components/bootstrap/dist/css/bootstrap.min.css" />
      
      <style type = "text/css">
         #contact-list {
            height: 150px;
            overflow-y: auto;
         }
      </style>
   </head>
   
   <body>
      <div class = "container">
         <h1>Enter Names and Email addresses of your contacts</h1>
         <div class = "form-group">
            <label for = "Name">Name</label>
            <input type = "text" name = "Name" value = "" id = "Name" 
               placeholder = "Name" class = "form-control" required>
         </div>
         
         <div class = "form-group">
            <label for = "Email">Email</label>
            <input type = "email" name = "Email" value = "" id = "Email" 
               placeholder = "Email" class = "form-control" required>
         </div>
         
         <div class = "form-group">
            <button class = "btn btn-primary" id = "add-to-list">Add to list!</button>
         </div>
         
         <div id = "contact-list">
            <table class = "table-striped" id = "contact-table">
               <tr>
                  <th class = "col-xs-2">S. No.</th>
                  <th class = "col-xs-4">Name</th>
                  <th class = "col-xs-6">Email</th>
               </tr>
            </table>
         </div>
         
         <script src = "./view.js" ></script>
      </div>
   </body>
</html>

现在我们需要处理添加事件。我们将在我们的view.js文件中执行此操作。

我们将创建一个loadAndDisplayContacts()函数,该函数将最初从文件中加载联系人。创建loadAndDisplayContacts()函数后,我们将为添加到列表按钮创建一个点击处理程序。这会将条目添加到文件和表格中。

在您的 view.js 文件中,输入以下代码:

let $ = require('jquery')
let fs = require('fs')
let filename = 'contacts'
let sno = 0

$('#add-to-list').on('click', () => {
   let name = $('#Name').val()
   let email = $('#Email').val()

   fs.appendFile('contacts', name + ',' + email + '\n')

   addEntry(name, email)
})

function addEntry(name, email) {
   if(name && email) {
      sno++
      let updateString = '<tr><td>'+ sno + '</td><td>'+ name +'</td><td>' 
         + email +'</td></tr>'
      $('#contact-table').append(updateString)
   }
}

function loadAndDisplayContacts() {  
   
   //Check if file exists
   if(fs.existsSync(filename)) {
      let data = fs.readFileSync(filename, 'utf8').split('\n')
      
      data.forEach((contact, index) => {
         let [ name, email ] = contact.split(',')
         addEntry(name, email)
      })
   
   } else {
      console.log("File Doesn\'t Exist. Creating new file.")
      fs.writeFile(filename, '', (err) => {
         if(err)
            console.log(err)
      })
   }
}

loadAndDisplayContacts()

现在运行应用程序,使用以下命令:

$ electron ./main.js

向其中添加一些联系人后,应用程序将如下所示:

File

有关更多fs 模块 API 调用,请参考Node 文件系统教程

现在我们可以使用 Electron 处理文件。我们将在对话框章节中了解如何在对话框中调用文件的保存和打开对话框(原生)。

Electron - 原生 Node 库

我们在上一章中使用了 Node 模块 fs。现在我们将了解一些其他可以与 Electron 一起使用的 Node 模块。

OS 模块

使用 OS 模块,我们可以获取有关应用程序正在运行的系统的大量信息。以下是一些在创建应用程序时有帮助的方法。这些方法帮助我们根据其运行的操作系统自定义应用程序。

序号 函数和说明
1

os.userInfo([options])

os.userInfo()方法返回有关当前有效用户的信息。此信息可用于个性化应用程序以供用户使用,即使无需明确请求信息。

2

os.platform()

os.platform()方法返回一个字符串,标识操作系统平台。这可用于根据用户操作系统自定义应用程序。

3

os.homedir()

os.homedir()方法将当前用户的 home 目录作为字符串返回。通常,所有用户的配置都位于用户的 home 目录中。因此,这可以用于我们应用程序的相同目的。

4

os.arch()

os.arch()方法返回一个字符串,标识操作系统 CPU 架构。这可以在异构架构上运行时使用,以使您的应用程序适应该系统。

5

os.EOL

一个字符串常量,定义操作系统特定的行尾标记。在主机操作系统上的文件中结束行时应使用此标记。

使用相同的主文件和以下 HTML 文件,我们可以在屏幕上打印这些属性:

<html>
   <head>
      <title>OS Module</title>
   </head>
   
   <body>
      <script>
         let os = require('os')
         document.write('User Info: ' + JSON.stringify(os.userInfo()) + '<br>' + 
            'Platform: ' + os.platform() + '<br>' + 
            'User home directory: ' +  os.homedir() + '<br>' + 
            'OS Architecture: ' + os.arch() + '<br>')
      </script>
   </body>
</html>

现在运行应用程序,使用以下命令:

$ electron ./main.js

以上命令将生成以下输出:

User Info: {"uid":1000,"gid":1000,"username":"ayushgp","homedir":"/home/ayushgp",
   "shell":"/usr/bin/zsh"}
Platform: linux
User home directory: /home/ayushgp
OS Architecture: x64

Net 模块

net 模块用于应用程序中的网络相关工作。我们可以使用此模块创建服务器和套接字连接。通常,建议使用 npm 的包装器模块而不是 net 模块来执行网络相关任务。

下表列出了该模块中最有用的方法:

序号 函数和说明
1

net.createServer([options][, connectionListener])

创建一个新的 TCP 服务器。connectionListener 参数会自动设置为 'connection' 事件的监听器。

2

net.createConnection(options[, connectionListener])

一个工厂方法,它返回一个新的 'net.Socket' 并连接到提供的地址和端口。

3

net.Server.listen(port[, host][, backlog][, callback])

开始在指定的端口和主机上接受连接。如果省略主机,则服务器将接受定向到任何 IPv4 地址的连接。

4

net.Server.close([callback])

当所有连接都结束且服务器发出 'close' 事件时,最终关闭。

5

net.Socket.connect(port[, host][, connectListener])

为给定的套接字打开连接。如果提供了端口和主机,则套接字将作为 TCP 套接字打开。

net 模块还带有一些其他方法。要获取更全面的列表,请参阅 此处

现在,让我们创建一个使用 net 模块创建到服务器的连接的 Electron 应用程序。我们需要创建一个新文件,server.js

var net = require('net');
var server = net.createServer(function(connection) { 
   console.log('Client Connected');
   
   connection.on('end', function() {
      console.log('client disconnected');
   });
   
   connection.write('Hello World!\r\n');
   connection.pipe(connection);
});

server.listen(8080, function() { 
   console.log('Server running on https://127.0.0.1:8080');
});

使用相同的 main.js 文件,将 HTML 文件替换为以下内容:

<html>
   <head>
      <title>net Module</title>
   </head>
   
   <body>
      <script>
         var net = require('net');
         var client = net.connect({port: 8080}, function() {
            console.log('Connection established!');  
         });
         
         client.on('data', function(data) {
            document.write(data.toString());
            client.end();
         });
         
         client.on('end', function() { 
            console.log('Disconnected :(');
         });
      </script>
   </body>
</html>

使用以下命令运行服务器:

$ node server.js

使用以下命令运行应用程序:

$ electron ./main.js

以上命令将生成以下输出:

Net Module

观察到我们自动连接到服务器并自动断开连接。

我们还有一些其他节点模块,可以使用 Electron 直接在前端使用。这些模块的使用取决于您在其中使用它们的场景。

Electron - 进程间通信

Electron 为我们提供了 2 个 IPC(进程间通信)模块,称为 ipcMainipcRenderer

ipcMain 模块用于从主进程异步通信到渲染器进程。在主进程中使用时,该模块处理从渲染器进程(网页)发送的异步和同步消息。从渲染器发送的消息将被发送到此模块。

ipcRenderer 模块用于从渲染器进程异步通信到主进程。它提供了一些方法,以便您可以从渲染器进程(网页)向主进程发送同步和异步消息。您还可以接收来自主进程的回复。

我们将创建一个主进程和一个渲染器进程,它们将使用上述模块相互发送消息。

创建一个名为 main_process.js 的新文件,内容如下:

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

let win

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

// Event handler for asynchronous incoming messages
ipcMain.on('asynchronous-message', (event, arg) => {
   console.log(arg)

   // Event emitter for sending asynchronous messages
   event.sender.send('asynchronous-reply', 'async pong')
})

// Event handler for synchronous incoming messages
ipcMain.on('synchronous-message', (event, arg) => {
   console.log(arg) 

   // Synchronous event emmision
   event.returnValue = 'sync pong'
})

app.on('ready', createWindow)

现在创建一个新的 index.html 文件并在其中添加以下代码。

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>Hello World!</title>
   </head>
   
   <body>
      <script>
         const {ipcRenderer} = require('electron')

         // Synchronous message emmiter and handler
         console.log(ipcRenderer.sendSync('synchronous-message', 'sync ping')) 

         // Async message handler
         ipcRenderer.on('asynchronous-reply', (event, arg) => {
            console.log(arg)
         })

         // Async message sender
         ipcRenderer.send('asynchronous-message', 'async ping')
      </script>
   </body>
</html>

使用以下命令运行应用程序:

$ electron ./main_process.js

以上命令将生成以下输出:

// On your app console
Sync Pong
Async Pong

// On your terminal where you ran the app
Sync Ping
Async Ping

建议不要在渲染器进程上执行繁重/阻塞任务的计算。始终使用 IPC 将这些任务委托给主进程。这有助于保持应用程序的运行速度。

Electron - 系统对话框

对于任何应用程序来说,用户友好性都非常重要。因此,您不应使用 alert() 调用创建对话框。Electron 提供了一个非常好的界面来完成创建对话框的任务。让我们来看一下。

Electron 提供了一个 dialog 模块,我们可以使用它来显示用于打开和保存文件、警报等的本机系统对话框。

让我们直接跳到一个示例,并创建一个应用程序来显示简单的文本文件。

创建一个新的 main.js 文件并在其中输入以下代码:

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

let win  

function createWindow() { 
   win = new BrowserWindow({width: 800, height: 600}) 
   win.loadURL(url.format ({ 
      pathname: path.join(__dirname, 'index.html'), 
      protocol: 'file:', 
      slashes: true 
   })) 
}  

ipcMain.on('openFile', (event, path) => { 
   const {dialog} = require('electron') 
   const fs = require('fs') 
   dialog.showOpenDialog(function (fileNames) { 
      
      // fileNames is an array that contains all the selected 
      if(fileNames === undefined) { 
         console.log("No file selected"); 
      
      } else { 
         readFile(fileNames[0]); 
      } 
   });
   
   function readFile(filepath) { 
      fs.readFile(filepath, 'utf-8', (err, data) => { 
         
         if(err){ 
            alert("An error ocurred reading the file :" + err.message) 
            return 
         } 
         
         // handle the file content 
         event.sender.send('fileData', data) 
      }) 
   } 
})  
app.on('ready', createWindow)

此代码将在我们的主进程从渲染器进程接收 'openFile' 消息时弹出打开对话框。此消息将文件内容重定向回渲染器进程。现在,我们将必须打印内容。

现在,创建一个新的 index.html 文件,内容如下:

<!DOCTYPE html> 
<html> 
   <head> 
      <meta charset = "UTF-8"> 
      <title>File read using system dialogs</title> 
   </head> 
   
   <body> 
      <script type = "text/javascript"> 
         const {ipcRenderer} = require('electron') 
         ipcRenderer.send('openFile', () => { 
            console.log("Event sent."); 
         }) 
         
         ipcRenderer.on('fileData', (event, data) => { 
            document.write(data) 
         }) 
      </script> 
   </body> 
</html>

现在,每当我们运行应用程序时,都会弹出一个本机打开对话框,如下面的屏幕截图所示:

Open Dialog

选择要显示的文件后,其内容将显示在应用程序窗口中:

File Read Using Dialog

这只是 Electron 提供的四个对话框之一。尽管它们都具有类似的用法。一旦您学会了如何使用 showOpenDialog,那么您就可以使用任何其他对话框。

具有相同功能的对话框有:

  • showSaveDialog([browserWindow, ]options[, callback])
  • showMessageDialog([browserWindow, ]options[, callback])
  • showErrorDialog(title, content)

Electron - 菜单

桌面应用程序带有两种类型的菜单——应用程序菜单(在顶部栏上)和上下文菜单(右键单击菜单)。我们将在本章中学习如何创建这两者。

我们将使用两个模块——MenuMenuItem 模块。请注意,MenuMenuItem 模块仅在主进程中可用。要在渲染器进程中使用这些模块,您需要 remote 模块。当我们创建上下文菜单时,我们将遇到它。

现在,让我们为主进程创建一个新的 main.js 文件:

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

let win

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

const template = [
   {
      label: 'Edit',
      submenu: [
         {
            role: 'undo'
         },
         {
            role: 'redo'
         },
         {
            type: 'separator'
         },
         {
            role: 'cut'
         },
         {
            role: 'copy'
         },
         {
            role: 'paste'
         }
      ]
   },
   
   {
      label: 'View',
      submenu: [
         {
            role: 'reload'
         },
         {
            role: 'toggledevtools'
         },
         {
            type: 'separator'
         },
         {
            role: 'resetzoom'
         },
         {
            role: 'zoomin'
         },
         {
            role: 'zoomout'
         },
         {
            type: 'separator'
         },
         {
            role: 'togglefullscreen'
         }
      ]
   },
   
   {
      role: 'window',
      submenu: [
         {
            role: 'minimize'
         },
         {
            role: 'close'
         }
      ]
   },
   
   {
      role: 'help',
      submenu: [
         {
            label: 'Learn More'
         }
      ]
   }
]

const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
app.on('ready', createWindow)

我们在这里从模板构建菜单。这意味着我们将菜单作为 JSON 提供给函数,它将处理其余部分。现在,我们必须将此菜单设置为应用程序菜单。

现在创建一个名为 index.html 的空 HTML 文件,并使用以下命令运行此应用程序:

$ electron ./main.js

在应用程序菜单的正常位置,您将看到一个基于上述模板的菜单。

Application Menus

我们从主进程创建了此菜单。现在让我们为我们的应用程序创建一个上下文菜单。我们将在我们的 HTML 文件中执行此操作:

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>Menus</title>
   </head>
   
   <body>
      <script type = "text/javascript">
         const {remote} = require('electron')
         const {Menu, MenuItem} = remote

         const menu = new Menu()

         // Build menu one item at a time, unlike
         menu.append(new MenuItem ({
            label: 'MenuItem1',
            click() { 
               console.log('item 1 clicked')
            }
         }))
         
         menu.append(new MenuItem({type: 'separator'}))
         menu.append(new MenuItem({label: 'MenuItem2', type: 'checkbox', checked: true}))
         menu.append(new MenuItem ({
            label: 'MenuItem3',
            click() {
               console.log('item 3 clicked')
            }
         }))

         // Prevent default action of right click in chromium. Replace with our menu.
         window.addEventListener('contextmenu', (e) => {
            e.preventDefault()
            menu.popup(remote.getCurrentWindow())
         }, false)
      </script>
   </body>
</html>

我们使用 remote 模块导入了 Menu 和 MenuItem 模块;然后,我们创建了一个菜单并将我们的菜单项逐一附加到其中。此外,我们阻止了 chromium 中右键单击的默认操作,并将其替换为我们的菜单。

Context Menu

在 Electron 中创建菜单是一项非常简单的任务。现在,您可以将您的事件处理程序附加到这些项目并根据您的需要处理事件。

Electron - 系统托盘

系统托盘是应用程序窗口外部的菜单。在 MacOS 和 Ubuntu 上,它位于屏幕的右上角。在 Windows 上,它位于右下角。我们可以使用 Electron 在系统托盘中为我们的应用程序创建菜单。

创建一个新的 main.js 文件并将以下代码添加到其中。准备好一个 png 文件以用于系统托盘图标。

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

let win

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

app.on('ready', createWindow)

在设置了基本浏览器窗口后,我们将创建一个新的 index.html 文件,内容如下:

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>Menus</title>
   </head>
   <body>
      <script type = "text/javascript">
         const {remote} = require('electron')
         const {Tray, Menu} = remote
         const path = require('path')

         let trayIcon = new Tray(path.join('','/home/ayushgp/Desktop/images.png'))

         const trayMenuTemplate = [
            {
               label: 'Empty Application',
               enabled: false
            },
            
            {
               label: 'Settings',
               click: function () {
                  console.log("Clicked on settings")
               }
            },
            
            {
               label: 'Help',
               click: function () {
                  console.log("Clicked on Help")
               }
            }
         ]
         
         let trayMenu = Menu.buildFromTemplate(trayMenuTemplate)
         trayIcon.setContextMenu(trayMenu)
      </script>
   </body>
</html>

我们使用 Tray 子模块创建了托盘。然后,我们使用模板创建了一个菜单,并将菜单进一步附加到我们的托盘对象。

使用以下命令运行应用程序:

$ electron ./main.js

当您运行上述命令时,请检查您的系统托盘以查看您使用的图标。我为我的应用程序使用了笑脸。上述命令将生成以下输出:

tray

Electron - 通知

Electron 仅为 MacOS 提供本机通知 API。因此,我们不会使用它,而是将使用名为 node-notifier 的 npm 模块。它允许我们向 Windows、MacOS 和 Linux 上的用户发送通知。

使用以下命令在您的应用程序文件夹中安装 node-notifier 模块:

$ npm install --save node-notifier

现在让我们创建一个应用程序,该应用程序有一个按钮,每次我们单击此按钮时都会生成一个通知。

创建一个新的 main.js 文件并在其中输入以下代码:

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

let win

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

app.on('ready', createWindow)

现在让我们创建网页和脚本,这些网页和脚本将触发通知。创建一个新的 index.html 文件,内容如下:

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>Menus</title>
   </head>
   
   <body>
      <button type = "button" id = "notify" name = "button">
         Click here to trigger a notification!</button>
      <script type = "text/javascript">
         const notifier = require('node-notifier')
         const path = require('path');
         
         document.getElementById('notify').onclick = (event) => {
            notifier.notify ({
               title: 'My awesome title',
               message: 'Hello from electron, Mr. User!',
               icon: path.join('','/home/ayushgp/Desktop/images.png'),  // Absolute path 
                  (doesn't work on balloons)
               sound: true,  // Only Notification Center or Windows Toasters
               wait: true    // Wait with callback, until user action is taken 
               against notification
            
            }, function (err, response) {
               // Response is response from notification
            });

            notifier.on('click', function (notifierObject, options) {
               console.log("You clicked on the notification")
            });

            notifier.on('timeout', function (notifierObject, options) {
               console.log("Notification timed out!")
            });
         }
      </script>
   </body>
</html>

notify 方法允许我们向其传递一个包含标题、消息、缩略图等信息的对象,这些信息有助于我们自定义通知。我们还可以为通知设置一些事件监听器。

现在,使用以下命令运行应用程序:

$ electron ./main.js

当您单击我们创建的按钮时,您将看到来自操作系统的本机通知,如下面的屏幕截图所示:

Notification

我们还处理了用户单击通知或通知超时时的事件。这些方法有助于使应用程序更具交互性,如果它在后台运行。

Electron - Webview

webview 标签用于在您的 Electron 应用程序中嵌入“访客”内容,例如网页。此内容包含在 webview 容器中。应用程序中嵌入的页面控制此内容的显示方式。

webview 在与您的应用程序不同的进程中运行。为了确保来自恶意内容的安全,webview 没有与您的网页相同的权限。这可以保护您的应用程序免受嵌入内容的侵害。您的应用程序和嵌入页面之间的所有交互都将是异步的。

让我们考虑一个示例来了解如何在我们的 Electron 应用程序中嵌入外部网页。我们将在应用程序的右侧嵌入 tutorialspoint 网站。创建一个新的 main.js 文件,内容如下:

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

let win

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

app.on('ready', createWindow)

现在我们已经设置了主进程,让我们创建将嵌入 tutorialspoint 网站的 HTML 文件。创建一个名为 index.html 的文件,内容如下:

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>Menus</title>
   </head>
   
   <body>
      <div>
         <div>
            <h2>We have the website embedded below!</h2>
         </div>
         <webview id = "foo" src = "https://tutorialspoint.com/" style = 
            "width:400px; height:480px;">
            <div class = "indicator"></div>
         </webview>
      </div>
      
      <script type = "text/javascript">
         // Event handlers for loading events.
         // Use these to handle loading screens, transitions, etc
         onload = () => {
            const webview = document.getElementById('foo')
            const indicator = document.querySelector('.indicator')

            const loadstart = () => {
               indicator.innerText = 'loading...'
            }

            const loadstop = () => {
               indicator.innerText = ''
            }

            webview.addEventListener('did-start-loading', loadstart)
            webview.addEventListener('did-stop-loading', loadstop)
         }
      </script>
   </body>
</html>

使用以下命令运行应用程序:

$ electron ./main.js

以上命令将生成以下输出:

Webview

webview 标签也可以用于其他资源。webview 元素具有一系列它发出的事件,这些事件在官方文档中列出。您可以使用这些事件来改进功能,具体取决于 webview 中发生的事情。

每当您从 Internet 嵌入脚本或其他资源时,建议使用 webview。建议这样做,因为它具有极大的安全优势,并且不会妨碍正常行为。

Electron - 音频和视频捕获

如果您正在构建用于屏幕共享、语音备忘录等的应用程序,则音频和视频捕获是重要的特性。如果您需要应用程序捕获个人资料图片,它们也很有用。

我们将使用 getUserMedia HTML5 API 来使用 Electron 捕获音频和视频流。首先让我们在 main.js 文件中设置我们的主进程,如下所示:

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

let win

// Set the path where recordings will be saved
app.setPath("userData", __dirname + "/saved_recordings")

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

app.on('ready', createWindow)

现在我们已经设置了主进程,让我们创建将捕获此内容的 HTML 文件。创建一个名为 index.html 的文件,内容如下:

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>Audio and Video</title>
   </head>
   
   <body>
      <video autoplay></video>
      <script type = "text/javascript">
         function errorCallback(e) {
            console.log('Error', e)
         }

         navigator.getUserMedia({video: true, audio: true}, (localMediaStream) => {
            var video = document.querySelector('video')
            video.src = window.URL.createObjectURL(localMediaStream)
            video.onloadedmetadata = (e) => {
               // Ready to go. Do some stuff.
            };
         }, errorCallback)
      </script>
   </body>
</html>

上述程序将生成以下输出:

Audio and Video Stream

您现在拥有来自网络摄像头和麦克风的流。您可以通过网络发送此流或以您喜欢的格式保存此流。

请查看MDN 文档,了解如何捕获图像以获取来自网络摄像头的图像并将其存储。这是使用 HTML5 的getUserMedia API 完成的。您还可以使用 Electron 附带的desktopCapturer模块捕获用户桌面。现在让我们看一个获取屏幕流的示例。

使用上面相同的 main.js 文件,并编辑 index.html 文件使其包含以下内容:

desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => {
   if (error) throw error
   for (let i = 0; i < sources.length; ++i) {
      if (sources[i].name === 'Your Window Name here!') {
         navigator.webkitGetUserMedia({
            audio: false,
            video: {
               mandatory: {
                  chromeMediaSource: 'desktop',
                  chromeMediaSourceId: sources[i].id,
                  minWidth: 1280,
                  maxWidth: 1280,
                  minHeight: 720,
                  maxHeight: 720
               }
            }
         }, handleStream, handleError)
         return
      }
   }
})

function handleStream (stream) {
   document.querySelector('video').src = URL.createObjectURL(stream)
}

function handleError (e) {
   console.log(e)
}

我们使用了desktopCapturer模块来获取每个打开窗口的信息。现在,您可以根据传递给上述if 语句的名称捕获特定应用程序或整个屏幕的事件。这只会将该屏幕上发生的内容流式传输到您的应用程序。

Desktop capturer

您可以参考此 StackOverflow 问题以详细了解其用法。

Electron - 定义快捷键

我们通常会记住我们在 PC 上每天使用的所有应用程序的某些快捷方式。为了使您的应用程序对用户感觉直观且易于访问,您必须允许用户使用快捷方式。

我们将使用 globalShortcut 模块在我们的应用程序中定义快捷方式。请注意,加速键是字符串,可以包含多个修饰符和键代码,并由 + 字符组合。这些加速键用于在整个应用程序中定义键盘快捷方式。

让我们考虑一个示例并创建一个快捷方式。为此,我们将遵循对话框示例,其中我们使用打开对话框打开文件。我们将注册一个CommandOrControl+O快捷方式以调出对话框。

我们的main.js代码将与之前相同。因此,创建一个新的main.js文件,并在其中输入以下代码:

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

let win

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600})
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
   }))
}

ipcMain.on('openFile', (event, path) => {
   const {dialog} = require('electron')
   const fs = require('fs')
   dialog.showOpenDialog(function (fileNames) {
         
      // fileNames is an array that contains all the selected
      if(fileNames === undefined)
         console.log("No file selected")
      else
         readFile(fileNames[0])
   })

   function readFile(filepath){
      fs.readFile(filepath, 'utf-8', (err, data) => {
         if(err){
            alert("An error ocurred reading the file :" + err.message)
            return
         }
         
         // handle the file content
         event.sender.send('fileData', data)
      })
   }
})

app.on('ready', createWindow)

每当我们的主进程从渲染器进程接收“openFile”消息时,此代码都会弹出打开对话框。之前,此对话框在应用程序运行时弹出。现在让我们将其限制为仅在我们按下CommandOrControl+O时打开。

现在创建一个新的index.html文件,内容如下:

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "UTF-8">
      <title>File read using system dialogs</title>
   </head>
   
   <body>
      <p>Press CTRL/CMD + O to open a file. </p>
      <script type = "text/javascript">
         const {ipcRenderer, remote} = require('electron')
         const {globalShortcut} = remote
         globalShortcut.register('CommandOrControl+O', () => {
            ipcRenderer.send('openFile', () => {
               console.log("Event sent.");
            })
            
            ipcRenderer.on('fileData', (event, data) => {
               document.write(data)
            })
         })
      </script>
   </body>
</html>

我们注册了一个新的快捷方式,并传递了一个回调函数,该函数将在我们按下此快捷方式时执行。我们可以根据需要注销快捷方式。

现在,一旦应用程序打开,我们将收到使用我们刚刚定义的快捷方式打开文件的消息。

Open dialog

可以通过允许用户为定义的操作选择自己的快捷方式来使这些快捷方式可自定义。

Electron - 环境变量

环境变量控制应用程序配置和行为,而无需更改代码。某些 Electron 行为由环境变量控制,因为它们在命令行标志和应用程序代码之前初始化。

Electron 中编码了两种环境变量——生产变量开发变量。

生产变量

以下环境变量旨在在打包的 Electron 应用程序的运行时使用。

序号 变量和描述
1

GOOGLE_API_KEY

Electron 包含一个硬编码的 API 密钥,用于向 Google 的地理编码 Web 服务发出请求。由于此 API 密钥包含在每个版本的 Electron 中,因此它通常会超出其使用配额。

要解决此问题,您可以在环境中提供自己的 Google API 密钥。在打开任何将进行地理编码请求的浏览器窗口之前,将以下代码放在您的主进程文件中:

process.env.GOOGLE_API_KEY = 'YOUR_KEY_HERE'
2

ELECTRON_RUN_AS_NODE

将进程作为正常的 Node.js 进程启动。

3

ELECTRON_FORCE_WINDOW_MENU_BAR(仅限 Linux)

不要在 Linux 上使用全局菜单栏。

开发变量

以下环境变量主要用于开发和调试目的。

序号 变量和描述
1

ELECTRON_ENABLE_LOGGING

将 Chrome 的内部日志打印到控制台。

2

ELECTRON_ENABLE_STACK_DUMPING

当 Electron 崩溃时,将堆栈跟踪打印到控制台。

3

ELECTRON_DEFAULT_ERROR_MODE

当 Electron 崩溃时显示 Windows 的崩溃对话框。

要将任何这些环境变量设置为 true,请在您的控制台中设置它。例如,如果要启用日志记录,请使用以下命令:

对于 Windows

> set ELECTRON_ENABLE_LOGGING=true

对于 Linux

$ export ELECTRON_ENABLE_LOGGING=true

请注意,您需要在每次重新启动计算机时都设置这些环境变量。如果要避免这样做,请将这些行添加到您的.bashrc文件中。

Electron - 调试

我们有两个进程运行我们的应用程序——主进程和渲染器进程。

由于渲染器进程是在我们的浏览器窗口中执行的,因此我们可以使用 Chrome Devtools 调试它。要打开 DevTools,请使用快捷键“Ctrl+Shift+I”或<F12>键。您可以查看此处如何使用 devtools。

当您打开 DevTools 时,您的应用程序将如下面的屏幕截图所示:

DevTools

调试主进程

Electron 浏览器窗口中的 DevTools 只能调试在该窗口中执行的 JavaScript(即网页)。要调试在主进程中执行的 JavaScript,您需要使用外部调试器并使用--debug--debug-brk开关启动 Electron。

Electron 将在指定的端口上侦听 V8 调试器协议消息;外部调试器需要在此端口上连接。默认端口为 5858。

使用以下命令运行您的应用程序:

$ electron --debug = 5858 ./main.js

现在您将需要一个支持 V8 调试器协议的调试器。您可以为此目的使用 VSCode 或 node-inspector。例如,让我们按照以下步骤设置 VSCode。请按照以下步骤进行设置:

下载并安装VSCode。在 VSCode 中打开您的 Electron 项目。

添加一个文件.vscode/launch.json,其中包含以下配置:

{
   "version": "1.0.0",
   "configurations": [
      {
         "name": "Debug Main Process",
         "type": "node",
         "request": "launch",
         "cwd": "${workspaceRoot}",
         "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
         "program": "${workspaceRoot}/main.js"
      }
   ]
}

注意 - 对于 Windows,请使用"${workspaceRoot}/node_modules/.bin/electron.cmd"作为runtimeExecutable

main.js中设置一些断点,并在调试视图中开始调试。当您遇到断点时,屏幕将如下所示:

Debugger

VSCode 调试器非常强大,将帮助您快速纠正错误。您还有其他选择,例如node-inspector,用于调试 Electron 应用程序。

Electron - 打包应用程序

打包和分发应用程序是桌面应用程序开发过程中的一个组成部分。由于 Electron 是一个跨平台的桌面应用程序开发框架,因此所有平台的应用程序打包和分发也应该是一种无缝的体验。

Electron 社区创建了一个项目,electron-packager,它为我们处理了相同的问题。它允许我们通过 JS 或 CLI 使用特定于操作系统的捆绑包(.app、.exe 等)打包和分发我们的 Electron 应用程序。

支持的平台

Electron Packager 在以下主机平台上运行:

  • Windows(32/64 位)
  • OS X
  • Linux(x86/x86_64)

它为以下目标平台生成可执行文件/捆绑包:

  • Windows(也称为 win32,适用于 32/64 位)
  • OS X(也称为 darwin)/ Mac App Store(也称为 mas)
  • Linux(适用于 x86、x86_64 和 armv7l 架构)

安装

使用以下命令安装 electron packager:

# for use in npm scripts
$ npm install electron-packager --save-dev

# for use from cli
$ npm install electron-packager -g

打包应用程序

在本节中,我们将了解如何从命令行运行打包程序。命令的基本形式为:

electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]

这将:

  • 查找或下载 Electron 的正确版本。

  • 使用该版本的 Electron 在<output-folder>/<appname>-<platform>-<arch>中创建一个应用程序。

在两种情况下可以省略--platform--arch。如果您改为指定--all,则将为所有有效的目标平台/体系结构组合创建捆绑包。否则,将为主机平台/体系结构创建一个单个捆绑包。

Electron - 资源

我们使用了以下资源来了解更多关于 Electron 的信息。我们在创建本教程时参考了这些资源。

最重要的资源是Electron 文档。该文档全面涵盖了框架的几乎所有功能和特性。它们足以帮助您构建应用程序。

electron-sample-apps存储库中也提供了一些非常好的 Electron 示例。

视频资源

使用 Web 语言的桌面应用程序

使用 JavaScript 和 Electron 进行快速跨平台桌面应用程序开发

博文

使用 Electron 构建桌面应用程序

使用 React 和 Electron 构建音乐播放器

使用 HTML、JS 和 Electron 创建您的第一个桌面应用程序

使用 Electron 创建跨平台桌面 Node 应用程序

广告