electron 基础学习笔记

1、创建项目

1、安装

npm install -g electron / cnpm install -g electron

2、项目搭建方式

2-1、 克隆一个仓库、快速启动一个项目

克隆示例项目的仓库
git clone https://github.com/electron/electron-quick-start
进入这个仓库
cd electron-quick-start
安装依赖并运行
npm install && npm start

2-2、electron-forge 搭建一个 electron 项目

electron-forge 相当于 electron 的一个脚手架,可以让我们更方便的创建、运行、打包
electron 项目。
Github 地址:https://github.com/electron-userland/electron-forge
官网地址:https://www.electronforge.io/

如果你电脑上面安装了最新版本的 nodejs 可以使用 npx 创建项目,如果你电脑上面
安装了 yarn 也可以使用 yarn 创建项目

创建项目
npx create-electron-app my-new-app
或者
yarn create electron-app my-new-app

运行项目
cd my-new-app
npm start

2-3、手动搭建一个 electron 项目

1、新建一个项目目录 例如:electrondemo
2、在 electrondemo 目录下面新建三个文件: index.html、main.js 、package.json
3、index.html 里面用 css 进行布局(以前怎么写现在还是怎么写)
4、在 main.js 中写如下代码:

const { app, BrowserWindow } = require("electron")
const path = require("path")
const createWindow=()=>{
const mainWindow = new BrowserWindow({width: 800, height: 600, });
mainWindow.loadFile(path.join(__dirname, 'index.html'));
// mainWindow.loadURL('https://github.com');
}
//监听 electron ready 事件创建窗口
app.on('ready', createWindow);
//监听窗口关闭的事件,关闭的时候退出应用,macOs 需要排除
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
//Macos 中点击 dock 中的应用图标的时候重新创建窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});

5、运行

electron . 

2、基础知识

1、Electron 运行的流程

2、Electron 主进程和渲染进程

 2.1、关于主进程和渲染器进程

        package.json 中定义的入口被称为主进程。 在主进程中实例化 BrowserWindow 创建的
Web 页面被称为渲染进程。一个 Electron 应用只有一个主进程,但是可以有多个渲染进程,
每个 Electron 中的 web 页面运行在它自己的渲染进程中。

        主进程使用 BrowserWindow 实例创建页面。 每个 BrowserWindow 实例都在自己的
渲染进程里运行页面。 当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终
止。

2.2、主进程和渲染进程实战演示

const { app, BrowserWindow } = require("electron");
const path = require("path");
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 600, height: 400
});
mainWindow.loadFile(path.join(__dirname, "index.html"));
const secondWindow=new BrowserWindow({
width: 300, height: 200, parent:mainWindow, })
secondWindow.loadFile(path.join(__dirname, "second.html"));
}
//监听应用的启动事件
app.on("ready", createWindow)
//监听窗口关闭的事件,关闭的时候退出应用,macOs 需要排除
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
//Macos 中点击 dock 中的应用图标的时候重新创建窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});

3、Electron 主 进 程 和 渲 染 进 程 中 使 用 Nodejs 以 及Nodejs 第三方模块

3.1、主进程中使用 Nodejs 模块

Electron 主进程中无需任何配置就可以使用 nodejs 模块。

3.2、渲染进程中使用 Nodejs 模块

1、BrowserWindow 中通过 preload 加载的 js 文件可以直接使用 nodejs 模块

2、如果不使用 preload 加载的 js,Electron5.x 之后没法在渲染进程中直接使用 nodejs,如
果我们想在渲染进程中使用 nodejs 的话需要进行如下配置。

https://www.electronjs.org/docs/latest/api/browser-window

mainWindow=new BrowserWindow({width:800,height:600, webPreferences: {
nodeIntegration: true,//nodeIntegration Boolean (可选) - 是否启用Node integration. 默认值为 false.
 contextIsolation:false
}});

3.3、BrowserWindow 中通过 preload 加载的 js 文件可以直接使用 nodejs 模块

在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成 Node, 此脚本都可以访
问所有 Node API 脚本路径为文件的绝对路径

main.js 主进程代码:

const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 800, height: 600, webPreferences:{
preload:path.join(__dirname, "renderer/preload.js")
}
});
mainWindow.loadFile(path.join(__dirname, "index.html"));
}

renderer/preload.js 渲染进程代码:

const fs =require("fs");
fs.readFile("package.json",(err,data)=>{
if(err){
console.log(err)
return;
}
console.log(data.toString());
})

3.4、渲染进程中直接使用 Nodejs 模块

如果不使用 preload 加载的 js,Electron5.x 之后没法在渲染进程中直接使用 nodejs,如果
我们想在渲染进程中使用 nodejs 的话需要进行如下配置。

mainWindow=new BrowserWindow({width:800,height:600, webPreferences: {
nodeIntegration: true, contextIsolation:false
}});

主进程:

const { app, BrowserWindow } = require("electron");
const path = require("path");
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 800, height: 600, webPreferences:{
// preload:path.join(__dirname, "renderer/preload.js"), nodeIntegration:true, contextIsolation:false
}
});
mainWindow.loadFile(path.join(__dirname, "index.html"));
//打开 DevTools
mainWindow.webContents.openDevTools();
}

渲染进程:

const fs =require("fs");
fs.readFile("package.json",(err,data)=>{
if(err){
console.log(err)
return;
}
console.log(data.toString());
})

3.5、渲染进程和主进程中使用 Nodejs 第三方模块

1、安装模块
2、引入模块
3、参考第三方文档使用

4、Electron 开启调试模式

mainWindow.webContents.openDevTools();

5、Electron 调用 h5 的拖放 api 结合Nodejs fs 模块实现拖放打开文件功能

5-1、H5 的拖放 api

https://www.w3cschool.cn/jsref/event-ondragover.html

1、拖拽事件的定义和用法:
ondragover 事件在可拖动元素或选取的文本正在拖动到放置目标时触发。
默认情况下,数据/元素不能放置到其他元素中。 如果要实现改功能,我们需要防止元
素的默认处理方法。我们可以通过调用 event.preventDefault() 方法来实现 ondragover 事
件。
注意: 为了让元素可拖动,需要使用 HTML5draggable 属性。
提示: 链接和图片默认是可拖动的,不需要 draggable 属性。

2、在拖放的过程中会触发以下事件:

在拖动目标上触发事件 (源元素):
ondragstart - 用户开始拖动元素时触发
ondrag - 元素正在拖动时触发
ondragend - 用户完成元素拖动后触发

释放目标时触发的事件:
ondragenter - 当被鼠标拖动的对象进入其容器范围内时触发此事件ondragover - 当某被拖动的对象在另一对象容器范围内拖动时触发此事件
ondragleave - 当被鼠标拖动的对象离开其容器范围内时触发此事件
ondrop - 在一个拖动过程中,释放鼠标键时触发此事件
注意: 在拖动元素时,每隔 350 毫秒会触发 ondragover 事件。

5-2、Electron 拖放打开文件

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="css/base.css">
</head>
<body>
   <div class="content" id="content">
   </div>
    <script src="./renderer/index.js"></script>
</body>
</html>

主进程main.js

const { app, BrowserWindow } = require("electron");
const path = require("path");

const createWindow = () => {    
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences:{
            nodeIntegration:true,  //开启渲染进程中使用nodejs
            contextIsolation:false //开启渲染进程中使用nodejs    In Electron 12, the default will bechanged to `true      
        }
    });

    //在渲染进程中开启调试模式
    mainWindow.webContents.openDevTools()

    mainWindow.loadFile(path.join(__dirname, "index.html"));
}

//监听应用的启动事件
app.on("ready", createWindow)

//监听窗口关闭的事件,关闭的时候退出应用,macOs 需要排除 
app.on('window-all-closed', () => {

    if (process.platform !== 'darwin') {
        app.quit();
    }
});

//Macos 中点击 dock 中的应用图标的时候重新创建窗口 
app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

渲染进程renderer文件夹下index.js

/*
https://www.w3cschool.cn/jsref/event-ondragover.html
    ondragenter - 当被鼠标拖动的对象进入其容器范围内时触发此事件
    ondragover - 当某被拖动的对象在另一对象容器范围内拖动时触发此事件
    ondragleave - 当被鼠标拖动的对象离开其容器范围内时触发此事件
    ondrop - 在一个拖动过程中,释放鼠标键时触发此事件
*/
const fs=require("fs");
window.onload=()=>{    
    var contentDom=document.querySelector("#content");
    //阻止默认行为
    contentDom.ondragenter=contentDom.ondragover=contentDom.ondragleave=()=>{
        return false;
    }
    contentDom.ondrop=(e)=>{
        // console.log(e)
        console.log(e.dataTransfer.files[0].path);
        var path=e.dataTransfer.files[0].path;
        fs.readFile(path,(err,data)=>{
            if(err){
                console.log(err);
                return false;
            }            
            contentDom.innerHTML=data
        })
    }
}

6、Electron 模块介绍、remote 模块、remote 调用 BrowserWindow 打开新窗口以及 Content-Security-Policy

6-1、Electron 主进程和渲染进程中的模块(介绍)

官方文档:https://www.electronjs.org/zh/docs/latest/api/app

 6-2、Electron remote 模块

remote 模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径。


Electron 中, 与 GUI 相关的模块(如 dialog, menu 等)只存在于主进程,而不在渲染进程中 。为了能从渲染进程中使用它们,需要用 ipc模块来给主进程发送进程间消息。使用 remote 模
块,可以调用主进程对象的方法,而无需显式地发送进程间消息,这类似于 Java 的 RMI。

注意:Electron10.x 之前可以直接使用 Remote 模块,Electron10.x 以后 Electron14.x 以前要使用 remote 模块的话必须得在 BrowserWindow 中通过 enableRemoteModule: true 开启Electron14.x 以后官方把内置的 remote 挪到了第三方模块里面,下面我们给大家看看如何在Electron 最新版本中使用@electron/remote 模块

6-3、Electron 渲染进程中通过 remote 模块调用主进程中的 BrowserWindow 打开新窗口

1、安装@electron/remote

npm install --save @electron/remote
或者
cnpm install --save @electron/remote
或者
yarn add @electron/remote

2、主进程中配置启用 remote 模块

const remote=require(’@electron/remote/main’)
remote.initialize()
remote.enable(mainWindow.webContents);

主进程配置详细代码

const { app, BrowserWindow } = require("electron");
const path = require("path");
//1、引入初始化remote模块
const remote=require('@electron/remote/main');
remote.initialize();

const createWindow = () => {
    const mainWindow = new BrowserWindow({
        width: 1000,
        height: 600,
        webPreferences: {            
            nodeIntegration: true,   //允许渲染进程使用nodejs
            contextIsolation: false  //允许渲染进程使用nodejs
        }
    });
    mainWindow.loadFile(path.join(__dirname, "index.html"));

    // mainWindow.loadURL("https://www.itying.com")
    //打开调试模式
    mainWindow.webContents.openDevTools();

    //2、启动remote模块
    remote.enable(mainWindow.webContents);
}

//监听应用的启动事件
app.on("ready", createWindow)

//监听窗口关闭的事件,关闭的时候退出应用,macOs 需要排除 
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

//Macos 中点击 dock 中的应用图标的时候重新创建窗口 
app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

3、渲染进程引入 remote 模块使用

const {  BrowserWindow } = require("@electron/remote");
const path = require("path");
window.onload=()=>{
    var btnDom=document.querySelector("#btn")
    btnDom.onclick=()=>{
        const secondWindow = new BrowserWindow({
            width: 300,
            height: 200,         
        });
        secondWindow.loadFile(path.join(__dirname, "second.html"));
    }
}

6-4、Electron 网页安全政策 Content-Security-Policy

详情参考:https://www.electronjs.org/zh/docs/latest/tutorial/security

Content Security Policy 简称 CSP 是一种网页安全政策


CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。
CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。


通俗的讲开启 CSP 后可以让浏览器自动禁止外部注入恶意脚本,增加网站的安全性能。

从 2.0 版本开始,如果使用 electron 的开发人员没有定义 Content-Security-Policy,Electron就会在 DevTool console 发出警告提示,如下图:

配置 Content-Security-Policy:

两种方式的规则都是一样的,default-src 代表默认规则,'self'表示限制所有的外部资源,只允许当前域名加载资源。

<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

一般情况下,默认规则可以包揽大多数的需求,但凡事都有例外,资源繁多的时候通常需要特殊配置,最值得关心的是 script 的安全,这至关重要,一旦被注入恶意 script,很多安全控制就会荡然无存,可以使用 script-src 这一指令设置:处。

<meta http-equiv="Content-Security-Policy" content="script-src 'self'">

例如我们要引入 google-analytics 分析流量,可以这样设置:

<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://www.google-analytics.com">

7、Electron 自定义软件顶部菜单、右键菜单以及绑定快捷键

7-1、Electron 自定义软件顶部菜单以及绑定快捷键

Electron 中 Menu 模块可以用来创建原生菜单,它可用作应用菜单和 context 菜单。
这个模块是一个主进程的模块,并且可以通过 remote 模块给渲染进程调用。

主进程引入menu.js

const { app, BrowserWindow } = require("electron");
const remote=require('@electron/remote/main');
remote.initialize();

const path = require("path");
const createWindow = () => {      
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences:{
            nodeIntegration:true,  //开启渲染进程中使用nodejs
            contextIsolation:false, //开启渲染进程中使用nodejs    
        }
    }); 
    //启动remote模块
    remote.enable(mainWindow.webContents)

    //在渲染进程中开启调试模式
    mainWindow.webContents.openDevTools()    
    mainWindow.loadFile(path.join(__dirname, "index.html"));

    //引入menu渲染进程
    require('./ipcMain/menu');
}

//监听应用的启动事件
app.on("ready", createWindow)

//监听窗口关闭的事件,关闭的时候退出应用,macOs 需要排除 
app.on('window-all-closed', () => {

    if (process.platform !== 'darwin') {
        app.quit();
    }
});

//Macos 中点击 dock 中的应用图标的时候重新创建窗口 
app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

menu.js 

const { Menu } = require("electron");

//https://www.electronjs.org/docs/api/menu-item
var menuTemplate=[
    {
        label:"文件",
        submenu:[
            {
                label:"新建",
                accelerator:"ctrl+n",
                click:()=>{
                    console.log("Ctrl+N")
                }
            },
            {
                label:"打开",
                accelerator:"ctrl+o",
                click:()=>{
                    console.log("Ctrl+O")
                }
            },
            {
                type:"separator"
            },
            {
                label:"保存"
            }
        ]
    },
    {
        label:"编辑",
        submenu:[
            {
                label:"复制",
                role:"copy",
                click:()=>{
                    console.log("copy")
                }
            },
            {
                label:"黏贴",
                role:"paste"
            }            
        ]
    }
];

var menuBuilder=Menu.buildFromTemplate(menuTemplate);

Menu.setApplicationMenu(menuBuilder);

7-2、Electron 自定义右键菜单

实现右键菜单有两种方法:
1、使用@electron/remote 模块实现
2、使用主进程和渲染进程通信实现

通过@electron/remote 模块实现

渲染进程

const remote = require('@electron/remote')
const Menu=remote.Menu;

var menuContextTemplate=[
    {
        label:"复制",
        role:"copy",
        click:()=>{
            console.log("copy")
        }
    },
    {
        label:"黏贴",
        role:"paste"
    },
    { type: 'separator' }, //分隔线
    {
        label:"其他功能",
        click:()=>{
            console.log("其他功能")
        }
    }, {
        label:"文件",
        submenu:[
            {
                label:"新建",
                accelerator:"ctrl+n",
                click:()=>{
                    console.log("Ctrl+N")
                }
            },
            {
                label:"打开",
                accelerator:"ctrl+o",
                click:()=>{
                    console.log("Ctrl+O")
                }
            },
            {
                type:"separator"
            },
            {
                label:"保存"
            }
        ]
    },
];
var menuBuilder=Menu.buildFromTemplate(menuContextTemplate);

window.onload=()=>{
    window.addEventListener("contextmenu",(e)=>{
        console.log("鼠标点击了右键")
        e.preventDefault()
        menuBuilder.popup({
            window:remote.getCurrentWindow()
        })
    },false)
}

使用主进程和渲染进程通信实现

 主进程main.js

const { app, BrowserWindow } = require("electron");
const path = require("path");

const createWindow = () => {    
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences:{
            nodeIntegration:true, //开启渲染进程中使用nodejs 
            contextIsolation:false, //开启渲染进程中使用nodejs              
        }
    });

    //在渲染进程中开启调试模式
    mainWindow.webContents.openDevTools()

    mainWindow.loadFile(path.join(__dirname, "index.html"));

    //自定义顶部菜单
    require('./ipcMain/ipcMain');
    require('./ipcMain/contextMenu');
}

//监听应用的启动事件
app.on("ready", createWindow)

//监听窗口关闭的事件,关闭的时候退出应用,macOs 需要排除 
app.on('window-all-closed', () => {

    if (process.platform !== 'darwin') {
        app.quit();
    }
});

//Macos 中点击 dock 中的应用图标的时候重新创建窗口 
app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

ipcMain文件夹下ipcMain.js模块

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

//接收渲染进程的通知
ipcMain.on("sendMsg",(e,data)=>{
    console.log(data);
    console.log(e);
})
//主进程接收到异步消息以后通知渲染进程

ipcMain.on("sendMsgReplay",(e,data)=>{
    console.log(data);   
    e.sender.send("replayRenderer","收到了消息") 
})
//接收同步消息
ipcMain.on("sendMsgReplaySync",(e,data)=>{
    console.log(data);   
    e.returnValue="我是主进程 已经收到了你的消息"    
})

ipcMain文件夹下contextMenu.js模块 

const { Menu,ipcMain,BrowserWindow } = require("electron");

var contextTemplate = [
    { label: '复制', role: 'copy' },
    { label: '黏贴', role: 'paste' },
    { type: 'separator' },
    //分隔线 
    { label: '其他功能', click: () => { console.log('click') } }
];

var menuBuilder=Menu.buildFromTemplate(contextTemplate);

//监听右键菜单
ipcMain.on("showContextmenu",()=>{
    //渲染进程中获取当前窗口的方法 remote.getCurrentWindow() 
    //主进程中获取当前窗口的方法 BrowserWindow.getFocusedWindow()
    menuBuilder.popup({window:BrowserWindow.getFocusedWindow()});
})

渲染进程ipcRenderer.js模块

const { ipcRenderer } = require('electron');
window.onload=()=>{
    var sendMsgDom=document.querySelector("#sendMsg");
    sendMsgDom.onclick=()=>{
       //渲染进程给主进程发送消息
       ipcRenderer.send("sendMsg","this is Renderer msg");
    }
   //渲染进程给主进程发送消息
    var sendMsgReplayDom=document.querySelector("#sendMsgReplay");    
    sendMsgReplayDom.onclick=()=>{        
        ipcRenderer.send("sendMsgReplay","this is Renderer msg - sendMsgReplay");
     }
     //监听主进程的广播
     ipcRenderer.on("replayRenderer",(e,data)=>{
        console.log(data)
     })
     
    //同步发送消息
     var sendMsgReplaySyncDom=document.querySelector("#sendMsgSync");    
     sendMsgReplaySyncDom.onclick=()=>{        
        var replay= ipcRenderer.sendSync("sendMsgReplaySync","this is Renderer msg - sendMsgReplaySync");
        console.log(replay);
     }

     //监听主进程发送过来的消息
     ipcRenderer.on("rendererMsg",(e,data)=>{
        console.log(data)
     })
     //右键菜单
     window.addEventListener("contextmenu", (e) => {
         e.preventDefault()
         //弹出右键菜单  menuBuilder.popup({window:remote.getCurrentWindow()});
         ipcRenderer.send("showContextmenu");
   }, false)
     

}

 

8、Electron 主进程与渲染进程之间的通信(同步通信、异步通信)

有时候我们想在渲染进程中通过一个事件去执行主进程里面的方法。或者在渲染进程中通知主进程处理事件,主进程处理完成后广播一个事件让渲染进程去处理一些事情。这个时候就
用到了主进程和渲染进程之间的相互通信。

Electron 主进程和渲染进程的通信主要用到两个模块:ipcMain ipcRenderer

ipcMain:当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息, 当然也有可能从主进程向渲染进程发送消息。
ipcRenderer: 使用它提供的一些方法从渲染进程 (web 页面) 发送同步或异步的消息到主进程。 也可以接收主进程回复的消息。

场景 1:渲染进程给主进程发送异步消息:

//渲染进程
const { ipcRenderer } = require('electron')
ipcRenderer.send('msg',{name:'张三'}); //异步
//主进程
const { ipcMain } = require('electron');
ipcMain.on('msg',(event,arg) => {
})

场景 2:渲染进程给主进程发送异步消息,主进程接收到异步消息以后通知渲染进程

//渲染进程
const { ipcRenderer } = require('electron')
ipcRenderer.send('msg',{name:'张三'}); //异步
//主进程
const { ipcMain } = require('electron');
ipcMain.on('msg',(event,arg) => {
event.sender.send('reply', 'pong');
})
//渲染进程
const { ipcRenderer } = require('electron')
ipcRenderer.on('reply', function(event, arg) {
console.log(arg); // prints "pong"
});

场景 3:渲染进程给主进程发送同步消息

//渲染进程
const { ipcRenderer } = require('electron')
const msg = ipcRenderer.sendSync('msg-a');
console.log(msg)
//主进程
ipcMain.on('msg-a',(event)=> {
event.returnValue = 'hello';
})

场景 4:主进程通知渲染进程执行操作

//主进程
BrowserWindow.getFocusedWindow().webContents.send('replay','news');
//渲染进程
const { ipcRenderer } = require('electron')
ipcRenderer.on('reply', function(event, arg) {
console.log(arg); // prints "news"
});

代码:

主进程ipcMain.js模块

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

//接收渲染进程的通知
ipcMain.on("sendMsg",(e,data)=>{
    console.log(data);
    console.log(e);
})
//主进程接收到异步消息以后通知渲染进程

ipcMain.on("sendMsgReplay",(e,data)=>{
    console.log(data);   
    e.sender.send("replayRenderer","收到了消息") 
})
//接收同步消息
ipcMain.on("sendMsgReplaySync",(e,data)=>{
    console.log(data);   
    e.returnValue="我是主进程 已经收到了你的消息"    
})

主进程menu.js模块

const { Menu,BrowserWindow } = require("electron");

//https://www.electronjs.org/docs/api/menu-item
var menuTemplate=[
    {
        label:"文件",
        submenu:[
            {
                label:"触发渲染进程里面的方法",               
                click:()=>{
                    BrowserWindow.getFocusedWindow().webContents.send("rendererMsg","触发渲染进程里面的方法--我是主进程")
                }
            },
            {
                label:"新建",
                accelerator:"ctrl+n",
                click:()=>{
                    console.log("Ctrl+N")
                }
            },
            {
                label:"打开",
                accelerator:"ctrl+o",
                click:()=>{
                    console.log("Ctrl+O")
                }
            },
            {
                type:"separator"
            },
            {
                label:"保存"
            }
        ]
    },
    {
        label:"编辑",
        submenu:[
            {
                label:"复制",
                role:"copy",
                click:()=>{
                    console.log("copy")
                }
            },
            {
                label:"黏贴",
                role:"paste"
            }            
        ]
    }
];

var menuBuilder=Menu.buildFromTemplate(menuTemplate);

Menu.setApplicationMenu(menuBuilder);

主进程main.js

const { app, BrowserWindow } = require("electron");
const path = require("path");

const createWindow = () => {    
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences:{
            nodeIntegration:true,  ///开启渲染进程中使用nodejs
            contextIsolation:false, //开启渲染进程中使用nodejs   
        }
    });

    //在渲染进程中开启调试模式
    mainWindow.webContents.openDevTools()

    mainWindow.loadFile(path.join(__dirname, "index.html"));

    //自定义顶部菜单
    require('./ipcMain/ipcMain');
    require('./ipcMain/menu');
}

//监听应用的启动事件
app.on("ready", createWindow)

//监听窗口关闭的事件,关闭的时候退出应用,macOs 需要排除 
app.on('window-all-closed', () => {

    if (process.platform !== 'darwin') {
        app.quit();
    }
});

//Macos 中点击 dock 中的应用图标的时候重新创建窗口 
app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

渲染进程

const { ipcRenderer } = require('electron');
window.onload=()=>{
    var sendMsgDom=document.querySelector("#sendMsg");
    sendMsgDom.onclick=()=>{
       //渲染进程给主进程发送消息
       ipcRenderer.send("sendMsg","this is Renderer msg");
    }

   //渲染进程给主进程发送消息
    var sendMsgReplayDom=document.querySelector("#sendMsgReplay");    
    sendMsgReplayDom.onclick=()=>{        
        ipcRenderer.send("sendMsgReplay","this is Renderer msg - sendMsgReplay");
     }
     //监听主进程的广播
     ipcRenderer.on("replayRenderer",(e,data)=>{
        console.log(data)
     })
     
    //同步发送消息
     var sendMsgReplaySyncDom=document.querySelector("#sendMsgSync");    
     sendMsgReplaySyncDom.onclick=()=>{        
        var replay= ipcRenderer.sendSync("sendMsgReplaySync","this is Renderer msg - sendMsgReplaySync");
        console.log(replay);
     }

     //监听主进程发送过来的消息
      
     ipcRenderer.on("rendererMsg",(e,data)=>{
        console.log(data)
     })
     
}

 9、Electron 渲染进程与渲染进程之间的通信

9-1、Electron渲染进程通过 localstorage给另一个渲染进程传值

localStorage.setItem(key,value);
localStorage.getItem(key);

 渲染进程index.js

const { ipcRenderer } = require("electron");
window.onload = () => {

    var btnDom = document.querySelector("#btn");
    btnDom.onclick = () => {

        //设置一个值
        localStorage.setItem("aid",1234);
        ipcRenderer.send("openNews")
    }
}

主进程ipcMain.js

const { BrowserWindow,ipcMain} = require("electron");
const path = require("path");

ipcMain.on("openNews",()=>{
    const mainWindow = new BrowserWindow({
        width: 400,
        height: 300,
        webPreferences: {
            nodeIntegration: true,  //开启渲染进程中使用nodejs
            enableRemoteModule:true  //启用Remote模块
        }
    });
    mainWindow.loadFile(path.join(__dirname, "../news.html"));
})

 渲染进程news.js

var aid=localStorage.getItem("aid");
console.log(aid)//1234

9-2、通过 BrowserWindow 和 webContents 模块实现渲染进程和渲染进程的通信

webContents 是一个 事件发出者.它负责渲染并控制网页,也是 BrowserWindow 对象的属性。

https://www.electronjs.org/docs/latest/api/web-contents

需要了解的几个知识点:
1、获取当前窗口的 id:

const winId = BrowserWindow.getFocusedWindow().id;

2、监听当前窗口加载完成的事件

win.webContents.on('did-finish-load',(event) => {
})

3、同一窗口之间广播数据

win.webContents.on('did-finish-load',(event) => {
win.webContents.send('msg',winId,'我是 index.html 的数据');
})

4、通过 id 查找窗口

let win = BrowserWindow.fromId(winId);

5、监听当前窗口加载完成的事件触发代码:

渲染进程ipcRenderer文件下index.js模块

const { ipcRenderer } = require("electron");
window.onload = () => {

    var btnDom = document.querySelector("#btn");
    btnDom.onclick = () => {

        ipcRenderer.send("openNews","1234")
    }
}

ipcMain主进程

const { BrowserWindow,ipcMain} = require("electron");
const path = require("path");

ipcMain.on("openNews",(event,data)=>{
    console.log(data)
    const newsWindow = new BrowserWindow({
        width: 400,
        height: 300,
        webPreferences: {
            nodeIntegration: true,  //开启渲染进程中使用nodejs
            contextIsolation:false, //开启渲染进程中使用nodejs
        }
    });
    newsWindow.loadFile(path.join(__dirname, "../news.html"));
    newsWindow.webContents.on('did-finish-load',(event) => { 

        newsWindow.webContents.send("toNews",data);
    })

})

渲染进程news.js模块

const { ipcRenderer } = require("electron");
ipcRenderer.on("toNews",(e,data)=>{
    console.log(data)
})

6、通过 id 查找窗口代码实现

渲染进程ipcRenderer文件夹下index.js模块

const { ipcRenderer } = require("electron");
window.onload = () => {

    var btnDom = document.querySelector("#btn");
    btnDom.onclick = () => {
        ipcRenderer.send("openNews","1234")
    }
}

//接收主进程传过来的值   主进程里面的数据来源于news这个渲染进程
ipcRenderer.on("toIndex",(event,data)=>{
    console.log(data)
})

主进程ipcMain模块

const { BrowserWindow,ipcMain} = require("electron");
const path = require("path");

var indexId;  //index渲染进程的Id

ipcMain.on("openNews",(event,data)=>{

    indexId=BrowserWindow.getFocusedWindow().id;

    console.log("indexId:",indexId);

    const newsWindow = new BrowserWindow({
        width: 400,
        height: 300,
        webPreferences: {
            nodeIntegration: true,  //开启渲染进程中使用nodejs
            contextIsolation:false, //开启渲染进程中使用nodejs    In Electron 12, the default will bechanged to `true         
            enableRemoteModule:true  //启用Remote模块
        }
    });
    newsWindow.loadFile(path.join(__dirname, "../news.html"));
    newsWindow.webContents.on('did-finish-load',(event) => { 

        newsWindow.webContents.send("toNews",data);
    })
    // var newsId=BrowserWindow.getFocusedWindow().id;
    // console.log("newsId:",newsId);

})


ipcMain.on("runIndexFn",(e,data)=>{
    console.log(data)
    console.log(indexId)
    //获取index对应的BrowserWindow
    let mainWin = BrowserWindow.fromId(indexId);
    mainWin.webContents.send("toIndex",data)
})

渲染进程news.js模块

const { ipcRenderer } = require("electron");
ipcRenderer.on("toNews",(e,data)=>{
    console.log(data)
})
window.onload = () => {
    var btnDom = document.querySelector("#btn");
    btnDom.onclick = () => {

        ipcRenderer.send("runIndexFn","this is news html")
    }
}

10、Electron Shell 模块在用户默认浏览器中打开 URL 以及 Electron webview

1、Electron shell 模块 在用户默认浏览器中打开URL的示例

https://www.electronjs.org/docs/latest/api/shell

var {shell}=require('electron')
shell.openExternal('https://github.com');

2、Electron 中的 webview

https://www.electronjs.org/docs/latest/api/webview-tag

Electron5.x 之后官方不建议使用 webview 标签,可以使用 iframe 替代 webview

<iframe id="myWebview" src="https://www.baidu.com" style="position:fixed;width:
100%;height: 100%; border: none;" >
</iframe>

3、窗口加载网页BrowserView

 mainWindow.setBrowserView(view)
  view.setBounds({ x: 0, y: 0, width: 300, height: 300 })
  view.webContents.loadURL('https://electronjs.org')

11、Electron dialog 弹出框

dialog 模块提供了 api 来展示原生的系统对话框,例如打开文件框,alert 框,所以 web 应用可以给用户带来跟系统应用相同的体验。

https://www.electronjs.org/docs/latest/api/dialog

 

 

 渲染进程dialog.js模块

var remote = require('@electron/remote');


//https://electronjs.org/docs/api/dialog
var errorDom = document.querySelector('#error');
var mesageBoxDom = document.querySelector('#mesageBox');
var openDialogDom = document.querySelector('#openDialog');
var saveDialogDom = document.querySelector('#saveDialog');
var mesageBoxSyncDom = document.querySelector('#mesageBoxSync');
var openDialogSyncDom = document.querySelector('#openDialogSync');
var showSaveDialogSyncDom = document.querySelector('#showSaveDialogSync');

errorDom.onclick = function () { 
    remote.dialog.showErrorBox("错误!","数据输入错误");
}

mesageBoxDom.onclick = function () {  
    remote.dialog.showMessageBox({
        type:"info",
        // buttons:["ok","no"],
        buttons:["确定","取消","其他"],
        title:"关于我们",
        message:`
        版本: 1.49.1 (user setup)
        提交: 58bb7b2331731bf72587010e943852e13e6fd3cf
        日期: 2020-09-16T23:27:51.792Z
        Electron: 9.2.1
        Chrome: 83.0.4103.122
        Node.js: 12.14.1
        V8: 8.3.110.13-electron.0
        OS: Windows_NT x64 6.1.7601`
    }).then((result)=>{
        console.log(result.response)
        if(result.response==0){
            console.log("点击了确定")
        }else if(result.response==1){
            console.log("点击了取消")
        }
    }).catch((err)=>{
        console.log(err)
    })
}

openDialogDom.onclick = function () {

    remote.dialog.showOpenDialog({
        title:"打开目录",
        // properties: ['openFile', 'multiSelections'] 
        // properties: ['openFile'] 
        properties:["openDirectory"]
    }).then((result)=>{
        console.log(result.canceled)
        console.log(result.filePaths)

    }).catch((err)=>{
        console.log(err)
    })
    
    console.log("打开文件...")
}

saveDialogDom.onclick = function () {

    remote.dialog.showSaveDialog({
        title:"保存文件",
        defaultPath:"aaa.txt",
        filters: [
            { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
            { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
            { name: 'Custom File Type', extensions: ['as'] },
            { name: 'All Files', extensions: ['*'] }
          ]
    }).then((result)=>{
        console.log(result.canceled)   
        console.log(result.filePath)   

    }).catch((err)=>{
        console.log(err)
    })
}


mesageBoxSyncDom.onclick=function(){
    var result=remote.dialog.showMessageBoxSync({
        type:"info",
        buttons:["ok","no"],        
        title:"关于我们",
        message:`
        版本: 1.49.1 (user setup)
        提交: 58bb7b2331731bf72587010e943852e13e6fd3cf
        日期: 2020-09-16T23:27:51.792Z
        Electron: 9.2.1
        Chrome: 83.0.4103.122
        Node.js: 12.14.1
        V8: 8.3.110.13-electron.0
        OS: Windows_NT x64 6.1.7601`
    })
    console.log(result);

    console.log("111");
}
openDialogSyncDom.onclick=function(){
   var result = remote.dialog.showOpenDialogSync({
        title:"打开目录",
        // properties: ['openFile', 'multiSelections'] 
        // properties: ['openFile'] 
        properties:["openDirectory"]
    })
    console.log(result);

    console.log("执行");
}

showSaveDialogSyncDom.onclick=function(){
    var result = remote.dialog.showSaveDialogSync({
        title:"保存文件",
        defaultPath:"aaa.txt",
        filters: [
            { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
            { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
            { name: 'Custom File Type', extensions: ['as'] },
            { name: 'All Files', extensions: ['*'] }
          ]
    })
    console.log(result);
    console.log("执行");
}

12、Electron 系统托盘 托盘右键菜单、托盘图标闪烁 点击右上角关闭按钮隐藏到托盘

12-1、Electron 创建任务栏图标以及任务栏图标右键菜单

var { Menu, Tray,app,BrowserWindow } = require('electron');
const path = require('path');
var appIcon = new Tray(path.join(__dirname,'lover.png'));
const menu = Menu.buildFromTemplate( [
{
label: '设置', click: function () {} //打开相应页面
},{
label: '帮助', click: function () {}
},{
label: '关于', click: function () {}
},{
label: '退出', click: function () {
//
BrowserWindow.getFocusedWindow().webContents().send('close-main-window');
app.quit();
}
}
])
appIcon.setToolTip('my best app');
appIcon.setContextMenu(menu);

12-2、监听任务栏图标的单击、双击事件

var { Menu, Tray,app,BrowserWindow } = require('electron');
var appIcon = new Tray(path.join(__dirname,'lover.png'));
appIcon.on('double-click',()=>{
console.log(win);
win.show();
})

12-3、Electron 点击右上角关闭按钮隐藏任务栏图标

const win = BrowserWindow.getFocusedWindow();
win.on('close',(e)=>{
console.log(win.isFocused());
if(!win.isFocused()){
win=null;
}else{
e.preventDefault(); /*阻止应用退出*/
win.hide(); /*隐藏当前窗口*/
}
})

12-4、Electron 实现任务栏闪烁图标

timer=setInterval(function() {
count++;
if (count%2 == 0) {
appIcon.setImage(path.join(__dirname,'empty.ico'))
} else {
appIcon.setImage(path.join(__dirname,'lover.png'))
}
}, 500)

代码:

主进程main.js

const { app, BrowserWindow } = require("electron");
const path = require("path");
//注意:把trayIcon 定义到全局
let trayIcon=null;
const createWindow = () => {
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600
    });

    mainWindow.loadFile(path.join(__dirname, "index.html"));

    require("./ipcMain/Tray");
}

//监听应用的启动事件
app.on("ready", createWindow)

//监听窗口关闭的事件,关闭的时候退出应用,macOs 需要排除 
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

//Macos 中点击 dock 中的应用图标的时候重新创建窗口 
app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

主进程Tray.js模块

const { Tray, Menu, BrowserWindow, app } = require('electron');
const path = require("path");
//创建系统托盘  
//注意:把trayIcon 定义到全局
trayIcon = new Tray(path.join(__dirname, "../static/lover.png"));

//系统托盘的右键菜单
var contextMenu = Menu.buildFromTemplate([
    {
        label: "系统设置",
        click: () => {
            console.log("setting")
        }
    },
    {
        label: "更新软件",
        click: () => {
            console.log("update")
        }
    },
    {
        label: "退出",
        click: () => {
            if (process.platform !== 'darwin') {
                app.quit();
            } else {
                app.exit();
            }
        }
    }
])

trayIcon.setToolTip("electron托盘");

trayIcon.setContextMenu(contextMenu);


//点击右上角关闭按钮隐藏任务栏图标
let win = BrowserWindow.getFocusedWindow()
win.on("close", (e) => {
    if (!win.isFocused()) {
        win = null;
    } else {
        //阻止默认行为
        e.preventDefault();
        win.hide();
    }

})

//监听托盘双击的事件
trayIcon.on("double-click", () => {
    win.show();
})


//实现闪烁图标
let count = 1;
let timer = setInterval(() => {
    count++;
    if (count % 2 == 0) {
        trayIcon.setImage(path.join(__dirname, '../static/empty.ico'))
    } else {
        trayIcon.setImage(path.join(__dirname, '../static/favicon2.ico'))
    }
}, 500)

13、Electron 实现消息通知、监听网络变化、网络变化弹出通知框

13-1、Electron 实现消息通知

Electron 里面的消息通知是基于 h5 的通知 api 实现的。

https://developer.mozilla.org/zh-CN/docs/Web/API/notification

const option = {
title: 'title', body: 'body',
icon: path.join('main-process/favicon2.ico')
}
const myNotification = new window.Notification(option.title,option);
myNotification.onclick = () =>{
console.log('clicked');
}

13-2、Electron 监听网络变化

https://www.electronjs.org/zh/docs/latest/tutorial/online-offline-events 

渲染进程正使用


//监听网络变化实现通知

window.addEventListener('online', function () {
    console.log('有网络了');
})

window.addEventListener('offline', function () {
    //其他参数查询Notification文档
    var option = {
        title: '网易邮箱',
        body: '网络异常,请检查您的网络'
    }
    var myNotification = new window.Notification(option.title, option);
    myNotification.onclick = function () {
        console.log('点击了');
    }
})

 14、Electron 注册全局快捷键(globalShortcut) 以及 clipboard 剪切板事件以及 nativeImage 模块

14-1、Electron 注册全局快捷键(globalShortcut)

主进程globalShortcut.js模块

var { globalShortcut, app } = require('electron');
app.on('ready', function () {

    //注册全局快捷键
    globalShortcut.register('ctrl+e', function () {
        console.log('ctrl+e');
    })
    globalShortcut.register('ctrl+d', function () {
        console.log('ctrl+d registed');
    })

    //检测快捷键是否注册功能
    console.log(globalShortcut.isRegistered('ctrl+e'));
})

app.on('will-quit', function () {
    //注销全局快捷键的监听
    globalShortcut.unregister('ctrl+e');
    globalShortcut.unregister('ctrl+d');
})

14-2、clipboard 剪切板事件 clipboard 模块以及 nativeImage模块

主进程clipboard.js模块

//clipboard模块可以在主进程里面使用 也可以在渲染进程里面使用


var { clipboard, nativeImage } = require('electron');

//复制
// clipboard.writeText('机器码')

// 粘贴
// clipboard.readText();  

var code = document.querySelector('#code');
var btn = document.querySelector('#btn');
var input = document.querySelector('#input');
code.onclick = function () {
    clipboard.writeText(code.innerHTML);
    alert('复制成功');   //写一个div提示
}
btn.onclick = function () {
    //获取复制的内容
    input.value = clipboard.readText();
}

//监听按钮点击复制图片的事件
var btncopyimg = document.querySelector('#btncopyimg');
btncopyimg.onclick = function () {
    //复制图片黏贴到我们页面上
    /*
    1.引入nativeImage

    2、创建一个nativeImage的对象

    */
    var image = nativeImage.createFromPath('static/favicon2.ico');
    //复制图片
    clipboard.writeImage(image);
    //粘贴图片
    var imgsrc = clipboard.readImage().toDataURL();
    console.log(imgsrc);   //base64的地址    
    //创建一个img标签 指定他的src
    var imgDom = new Image();
    imgDom.src = imgsrc;
    document.body.appendChild(imgDom);
}

15、Electron 第三方模块 electron-log 的使用

electron-log 模块是一个非常简单的 electorn 日志模块,可以打印日志、保存日志、还可以把软件日志同步到远程服务器,您可以在主进程和渲染器进程中直接使用此模块。log 支持
的日志级别有:error, warn, info, verbose, debug, silly

地址:https://github.com/megahertz/electron-log

15-1、安装

npm install electron-log

15-2、使用

const log = require('electron-log');
log.info('Hello, log');
log.warn('Some problem appears');

15-3、修改本地日志输出目录

日志默认输出目录:

on Linux: ~/.config/{app name}/logs/{process type}.log
on macOS: ~/Library/Logs/{app name}/{process type}.log
on Windows: %USERPROFILE%\AppData\Roaming\{app name}\logs\{process type}.log

设置日志文件的路径:

log.transports.file.file = 'd:/myLog.log'

15-4、关闭日志

log.transports.file.level = false; //关闭文件日志,默认是开启的
log.transports.console.level = false; //关闭控制台日志,默认是开启的

15-5、使用第三方模块 electron-log 把日志发送到远程服务器

参考:https://github.com/megahertz/electron-log/blob/HEAD/docs/remote.md

Electron 中的配置:

log.transports.remote.level = 'warn';
log.transports.remote.url = 'https://example.com/myapp/add-log'
log.warn('Some problem appears', { error: e });

Post 的数据格式:

POST /myapp/add-log HTTP/1.1
Content-Length: 300
Content-Type: application/json
Host: example.com
Connection: close
{ "client": { "name": "electron-application" }, "data": [ "Some problem appears", { "error": { "constructor": "Error", "stack": "Error: test\n at App.createWindow ..."
}}
],"date": 1574238042989, "level": "warn", "variables": { "processType": "browser" }
}

NodeJs Express 后台接收

const express = require('express')
var bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.post("/addLog", (req, res) => {
console.log(req.body);
res.send("接收 post 的日志信息")
})
app.listen(3000)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值