electron

主进程和渲染进程

electron分为主进程渲染进程,以下是主进程和渲染进程区别

  • 主进程可系统API:可以调用系统对接Electron Api,例如创建菜单、上传文件、发送通知、文件操作、托盘等操作
  • 主进程职责:创建渲染进程(renderer Process)、全面使用NodeJs语法特性,一个electron只能有一个入口点。
  • 渲染进程(Renderer Process):可以有多个,每个对应一个窗口。每个都是一个单独的进程,全面支持NodeJs和Dom API,可使用一部分Electorn API。
123.png

第一个Electron项目

1.初始化项目

#创建一个目录
mkdir my-electron-app && cd my-electron-ap
#初始化项目描述
npm init
#安装electron依赖
npm install electron --save-dev
#nodemon 可监控文件变化,自动运行electron命令
npm install nodemon --save-dev
#安装devtron,它可以通过可视化方式查看IPC信息,在js中使用 require('devtron').install()
npm install devtron --save-dev
#安装打包工具
npm install electron-builder --save-dev
#安装压缩工具,解压 asar extract app.asar
npm install -g asar 

2.初始化项目package.json配置信息

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Hello World!",
  "main": "main.js",
  "scripts": {
    "start": "nodemon --watch main.js --exec \"electron .\"", //启动electron
    "build": "electron-builder"
  },
  "author": "Jane Doe",
  "license": "MIT",
  "devDependencies": {
    "electron": "23.1.3"
  }
}

3.创建一个main.js程序入口文件,内容如下代码

//引入electron模块,并且导入app对象和browserWindow对象
//app 对象负责应用程序的事件生命周期
//BrowserWindow 对象负责创建和管理应用窗口
const { app, BrowserWindow } = require('electron')


//创建窗口函数
const createWindow = () => {
  //创建浏览器窗口 
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })
 //加载本地界面
  win.loadFile('index.html')
  //打开开发者工具
  win.webContents.oepnDevTools() 
}
//应用启动时加载回调事件
app.whenReady().then(() => {
  //开启调试工具,监测IPC信息
  require('devtron').install()  
  //创建渲染窗口 
  createWindow()
})

//或者使用应用启动时加载回调事件
//app.on("ready",()=>{
//    createWindow()
//})

//关闭应用
app.quit()

4.创建入口文件index.html界面

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>my is Electron renderer!</title>
  </head>
  <body>
    <h1>my is Electron renderer!</h1>
    <p>ok</p>
  </body>
</html>

窗口对象

BrowserWindow对象代表窗口对象,主窗口对象只能存在一个。

//主窗口
const win = new BrowserWindow({
    width: 800, //窗口宽度
    height: 600, //窗口高度
    x:100, //窗口在屏幕x坐标位置
    y:100, //窗口在屏幕y坐标位置
    show:false, //不显示窗口
    maxHeight:1000, //最大高度
    maxWidth:1000, //最大宽度
    minHeight:200, //最小高度
    minWidth:300, //最小高度
    resizeable:false, //限制窗口不能缩放
    title:"我的electron", //设置窗口标题
    icon:'my.icon',//设置图标
    frame:false,//设置标题栏,false隐藏掉,true显示
    transparent:true,//开启透明窗体
    autoHideMenuBar:true,//是否显示菜单栏,true显示,false隐藏
    modal:false, //是否开启父子及模式窗口,如果开启模式窗口需要关闭子窗口才能操作主窗口
    webPreferences:{ //设置web窗口预选参数配置
        // 开启node模块
        nodeIntegration:true,
        //开启远程模块,让渲染窗口能使用electron Api函数
        enableRemoteModule:true,
        preload:path.join(__dirname,'preload.js'), //预加载js
        contextIsolation:false, //设置此项为false,才可在渲染进程中使用electron api
        webSecurity:false //去掉跨越验证
    }
  })
 //加载到渲染进程中
  win.loadFile('index.html')
  win.on('ready-to-show',()=>{
      //显示窗口
      win.show()
  })

//创建子创建
let secondWin = new BrowserWindow({
    width: 300, //窗口宽度
    height: 300, //窗口高度
    webPreferences:{ //设置web窗口预选参数配置
        // 开启node模块
        nodeIntegration:true,
        //开启远程模块,让渲染窗口能使用electron Api函数
        enableRemoteModule:true
    },
    parent:win //子窗口绑定到父窗口对象上
  })
//加载到渲染进程中
 secondWin.loadFile('second.html')

//----------------------------------------------------
//窗口对象中常用函数
const {remote} = require('electron')

//获取当前窗口句柄
let mainWin = remote.getCurrentWindow()
//关闭窗口
mainWin.close()
//获取窗口是否最大化
mainWin.isMaximized()
//设置窗口最大化
mainWin.maximize()
//将窗口还原
mainWin.restore()
//获取窗口是否最小化
mainWin.isMinimize()
//设置窗口最小化
mainWin.minimize()

//防止窗口关闭,关闭渲染窗口回调事件
window.onbeforeunload =()=>{
    return false;
}

//销毁窗口
mainWin.destroy()



//或者使用window.open打开页面
window.open('index.html','_self')

生命周期

8b6e5736ef77fabb44c0f19d3fbe336e.png
//----------------------------------应用的生命周期-------------------------------------
//应用加载加载前,可接受proctl传递参数
app.on('will-finish-launching',(event)=>{
  app.on('open-url', (event, url) => {
    log(`==> app-event: open-url <===`, url);
  });
})

//当【首次启动应用程序】、【在程序已经运行后再次打开程序】或【单击应用程序的坞站或任务栏图标时】重新激活它
app.on('did-become-active',()=>{
    
})

//应用加载完成通知
app.on('ready',()=>{
    
})

//当应用全部关闭时通知
app.on('window-all-closed',()=>{
    
})

app.on('before-quit', function (event) {
    // event.preventDefault() {2}
    console.log('before-quit')
})
app.on('will-quit', function (event) {
    // event.preventDefault()
    console.log('will-quit')
})

//当Windows 系统中,如果应用程序因系统关机/重启或用户注销而关闭,那么 before-quit和 quit 事件不会被触发
app.on('quit',()=>{
    
})


//-----------------------------------窗口生命周期-----------------------------------

mainWindow.on('close', () => {
    console.log('close - 窗口关闭,回收窗口引用')
    mainWindow = null
})
// webContents - 渲染以及控制 web 页面
mainWindow.webContents.on('did-finish-load', ()=>{
    console.log('did-finish-load - 导航完成时触发,即选项卡的旋转器将停止旋转,并指派onload事件后')
})

mainWindow.webContents.on('dom-ready', ()=>{
    console.log('dom-ready - dom准备好了,可以选择界面上的元素')
})


//html的docment元素渲染时加载的事件
window.addEventListener('DOMContentLoaded',()=>{
  //获取nodejs版本号
  console.log(process.versions.node)
})

生命周期执行顺序

will-finish-launching
open-url
did-become-active
ready - electron 初始化完成时触发
dom-ready - dom准备好了,可以选择界面上的元素
did-finish-load - 导航完成时触发,即选项卡的旋转器将停止旋转,并指派onload事件后
close - 窗口关闭,回收窗口引用
window-all-closed
before-quit
will-quit
quit

进程之间的通讯之IPC方式

  • Electron使用IPC(interprocess communication)在进程之间进行通信

  • 为什么需要进程之间通讯:因为渲染进程在使用electron API有一定的限制,需要通过IPC通信给主进程调用一些限定API。

微信截图_20231213232321.png

以下代码是渲染进程与主进程相互通讯

//------------------------------------------------------------
//渲染进程界面中的js代码
const {ipcRenderer} = require('electron')

window.addEventListener('DOMContentLoaded',()=>{
    //渲染进程发送消息到主进程
    ipcRenderer.send('message','my renderer')
    //监听主进程向子进程发送信息
    ipcRenderer.on('reply',(event,arg)=>{
        console.log(event)
        console.log(arg)
    })
})

//-------------------------------------------------------------
//主进程代码
const { app, BrowserWindow,ipcMain } = require('electron')

app.on('ready',()=>{
 const win = new BrowserWindow({
    width: 800, //窗口宽度
    height: 600, //窗口高度
    webPreferences:{ //设置web窗口预选参数配置
        // 开启node模块
        nodeIntegration:true,
        //开启远程模块,让渲染窗口能使用electron Api函数
        enableRemoteModule:true
    }
  })
 //加载到渲染进程中
  win.loadFile('index.html')
  //主进程监听ipc消息
  ipcMain.on('message',(event,arg)=>{
       console.log(event)
       console.log(arg)
       //主进程将消息发送到渲染进程中
       event.reply('reply','hello')
  })  
})

进程之间的通讯之remote方式

提供一种remote方式,它是最简单的主进程和渲染进程的通信。

//在渲染进程调用创建窗口
const {BrowserWindow}  = require('electron').remote

const win = new BrowserWindow({
    width: 300, //窗口宽度
    height: 200, //窗口高度
    webPreferences:{ //设置web窗口预选参数配置
        // 开启node模块
        nodeIntegration:true,
        //开启远程模块,让渲染窗口能使用electron Api函数
        enableRemoteModule:true
    }
})
 win.loadFile('index.html')

进程之间的通讯之第三种方式

let windowId = win.id
let mainWin = BrowserWindow.fromId(windowId)
mainWin.webContents.send('mti','1111')

electron自定菜单

const { app, BrowserWindow , Menu } = require('electron')
//打印当前操作系统
console.log(process.platform)
let menuTemp = [
    {label:'文件',submenu:[ //定义子菜单
        {label:'打开文件',
         click(){ //点击事件
             
         }
        },
        {type:'separator'},
    ]},
    {label:'编辑'},
    {
        lable:'角色',
        submenu:[
            {label:'复制',role:'copy',icon:'./1.png'},
            {label:'剪切',role:'cut'},
            {label:'粘贴',role:'paste'},
            {label:'最小化',role:'minimize'}
        ]    
    },
    {
        lable:'多选菜单',
        submenu:[
            {label:'选项1',type:'checkbox'},
            {label:'选项2',role:'checkbox'},
            {label:'选项3',role:'checkbox'},
            {type:'separator'},
            {label:'item',type:'radio'},
            {label:'window',type:'submenu',role:'windowMenu'},
        ]   
    }
]

//生成一个菜单项
let menu = Menu.BuildFromTemplate(menuTemp)
//将上述的模板绑定到应用
Menu.setApplicationMenu(menu)

electron右键菜单

const { remote } = require('electron')

const Menu = remote.Menu


let contextTemp = [
    {
        label:'Run Code',
        click(){
            
        }
    }
]

//创建菜单对象
let menu = Menu.buildFromTemplate(contextTemp)

window.addEventListener('DOMContentLoaded',()=>{
    window.addEventListener('contextmenu',(env)=>{
        env.preventDefault()
        //按popup方式打开
        menu.popup({window:remote.getCurrentWindow()})
    },false)
})

查找窗口句柄方式

//第一种
// 获取窗口的 ID
let windowId = win.id
BrowserWindow.fromId(windowId)
//第二种
remote.getCurrentWindow()

Dialog模块

弹出对话框窗口

const {remote} = require('electron')

window.onload=()=>{
    let btn1 = document.getElementById('btn1')
    let btn2 = document.getElementById('btn2')
    let btn3 = document.getElementById('btn3')
    
    
     //打开文件选择器窗口
    btn1.addEventListener('click',()=>{
        remote.dialog.showOpenDialog({
            defaultPath:__dirname, //设置当前目录下
            buttonLabel:'请选择', //设置按钮名称
            title:'my opendialog', //设置文件标题名称
            //properties:['openFile'], //选择文件
            properties:['openDirectroy'], //选择目录
            filters: [ //过滤条件
                { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
                { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
                { name: 'Custom File Type', extensions: ['as'] },
                { name: 'All Files', extensions: ['*'] }
            ]
        }).then((ret)=>{ //选择文件返回数据
            console.log(ret)
        }).catch((err)=>{ //错误信息
           console.log(err) 
        })
    })
    
     //打开保存对话框
    btn2.addEventListener('click',()=>{
        remote.dialog.showSaveDialog({
            defaultPath:__dirname, //设置当前目录下
            buttonLabel:'保存', //设置按钮名称
            title:'my saveDialog', //设置标题名称
            //properties:['openFile'], //选择文件
            properties:['showHiddenFiles'], //显示隐藏文件
            filters: [ //过滤条件
                { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
                { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
                { name: 'Custom File Type', extensions: ['as'] },
                { name: 'All Files', extensions: ['*'] }
            ]
        }).then((ret)=>{ //成功返回数据
            console.log(ret)
        }).catch((err)=>{ //错误信息
           console.log(err) 
        })
    })
    
     //打开提示对话框
    btn3.addEventListener('click',()=>{
        remote.dialog.showMessageBox({
            type:'info',//none, info, error, question
            message :'保存成功', //设置按钮名称
            title:'my saveDialog', //设置标题名称
        }).then((ret)=>{ //成功返回数据
            console.log(ret)
        }).catch((err)=>{ //错误信息
           console.log(err) 
        })
    })
    
    
}

shell模块

shell 模块提供与桌面集成相关的功能。

const { shell } = require('electron')

//打开系统默认浏览器
shell.openExternal('https://github.com')

//打开资源管理器
shell.showItemInFolder('c:/test')

//打开系统目录文件或文件
shell.openPath('c:/test/1.exe')

//移动到回收站
shell.trashItem('c:/test')

//播放哔哔的声音.
shell.beep()

//创建一个快捷图标
const shortcutPath = 'path/to/shortcut.lnk';

shell.writeShortcutLink(shortcutPath, 'create', {
  target: process.execPath,
  args: ['--myarg'],
  workingDirectory: 'path/to/app',
  icon: 'path/to/app/icon.ico',
  description: 'My Electron App Shortcut'
});

路径操作

__dirname 表示当前编写的文件所处的目录
path.basename() 返回路径的最后一部分
path.dirname() 返回路径的目录部分
path.extname() 返回路径的扩展名部分。
path.isAbsolute() 如果是绝对路径,则返回 true。
path.join() 连接路径的两个或多个部分
path.normalize() 当包含类似 .、.. 或双斜杠等相对的说明符时,则尝试计算实际的路径
path.parse() 解析对象的路径为组成其的片段
root: 根路径。
dir: 从根路径开始的文件夹路径。
base: 文件名 + 扩展名
name: 文件名
ext: 文件扩展名
path.relative()接受 2 个路径作为参数。 基于当前工作目录,返回从第一个路径到第二个路径的相对路径。
path.resolve()可以使用 path.resolve() 获得相对路径的绝对路径计算

文件操作

const fs = require('fs')
//创建新的文件夹
fs.mkdir('creatdir', 0777, function(err){
 if(err){
  console.log(err);
 }else{
  console.log("creat done!");
 }
})


//读取文件
fs.readFile('path/to/file', 'utf-8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log(data); // 文件内容
});


//写入文件
const content = 'Hello, World!';

fs.writeFile('path/to/file', content, 'utf-8', (err) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log('文件写入成功');
});


//复制文件
const sourcePath = 'path/to/source/file';
const destinationPath = 'path/to/destination/file';

fs.copyFile(sourcePath, destinationPath, (err) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log('文件复制成功');
});

//移动文件

const sourcePath = 'path/to/source/file';
const destinationPath = 'path/to/destination/file';

fs.rename(sourcePath, destinationPath, (err) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log('文件移动成功');
});


//删除文件
const filePath = 'path/to/file';

fs.unlink(filePath, (err) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log('文件删除成功');
});

屏幕信息

//参考文档 https://www.electronjs.org/zh/docs/latest/api/screen

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

let mainWindow = null

app.whenReady().then(() => {
  // Create a window that fills the screen's available work area.
  const primaryDisplay = screen.getPrimaryDisplay()
  const { width, height } = primaryDisplay.workAreaSize

  mainWindow = new BrowserWindow({ width, height })
  mainWindow.loadURL('https://electronjs.org')
})

渲染以及控制 web 页面

//参考文档 https://www.electronjs.org/zh/docs/latest/api/web-contents
const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')

//获取web上下文
const contents = win.webContents
console.log(contents)

//加载http网页
const options = { extraHeaders: 'pragma: no-cache\n' }
win.webContents.loadURL('https://github.com', options)

//加载文件
const win = new BrowserWindow()
win.loadFile('src/index.html')

//插入样式到web页面
win.webContents.on('did-finish-load', async () => {
  const key = await win.webContents.insertCSS('html, body { background-color: #f00; }')
  win.webContents.removeInsertedCSS(key)
})

//执行页面中JavaScript脚本
win.webContents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true)
  .then((result) => {
    console.log(result) // Will be the JSON object from the fetch call
  })


控制页面和内联框架(iframes)

//参考文档 https://www.electronjs.org/zh/docs/latest/api/web-frame-main

const { BrowserWindow, webFrameMain } = require('electron')

const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://twitter.com')

win.webContents.on(
  'did-frame-navigate',
  (event, url, httpResponseCode, httpStatusText, isMainFrame, frameProcessId, frameRoutingId) => {
    const frame = webFrameMain.fromId(frameProcessId, frameRoutingId)
    if (frame) {
      const code = 'document.body.innerHTML = document.body.innerHTML.replaceAll("heck", "h*ck")'
      frame.executeJavaScript(code)
    }
  }
)

发通知

const {Notification} = require('electron')

new Notification({
    title:'我的通知',
    body:'系统发送一条新的消息请注意查收'
}).show()

系统托盘

//参考文档https://www.electronjs.org/zh/docs/latest/api/tray
const { app, Menu, Tray } = require('electron')

let tray = null
app.whenReady().then(() => {
  tray = new Tray('/path/to/my/icon')
  const contextMenu = Menu.buildFromTemplate([
    { label: 'Item1', type: 'radio' },
    { label: 'Item2', type: 'radio' },
    { label: 'Item3', type: 'radio', checked: true },
    { label: 'Item4', type: 'radio' }
  ])
  tray.setToolTip('This is my application.')
  tray.setContextMenu(contextMenu)
})

录屏模块

//参考文档https://www.electronjs.org/zh/docs/latest/api/desktop-capturer

项目架构

#vite+electron+vue3+typeScript
npm install vite electron-vite --template vue-ts

其他web桌面技术

https://tauri.app/
https://nwjs.io/
//忽略https安全验证
app.commandLine.appendSwitch('--ignore-certificate-errors','true')
app.commandLine.appendSwitch('--disable-web-security','true')
//允许https混合加载http的资源
app.commandLine.appendSwitch('--allow-running-insecure-content','true')
//允许私有安全策略
app.commandLine.appendSwitch('--disable-features=BlockInsecurePrivateNetworkRequests','true')
app.commandLine.appendSwitch('--flag-switches-begin','true')
app.commandLine.appendSwitch('--flag-switches-end','true')
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
其它技术
electron-shared 
miniblink
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354