主进程和渲染进程
electron分为主进程和渲染进程,以下是主进程和渲染进程区别
- 主进程可系统API:可以调用系统对接Electron Api,例如创建菜单、上传文件、发送通知、文件操作、托盘等操作
- 主进程职责:创建渲染进程(renderer Process)、全面使用NodeJs语法特性,一个electron只能有一个入口点。
- 渲染进程(Renderer Process):可以有多个,每个对应一个窗口。每个都是一个单独的进程,全面支持NodeJs和Dom API,可使用一部分Electorn API。
第一个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。
以下代码是渲染进程与主进程相互通讯
//------------------------------------------------------------
//渲染进程界面中的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
网友评论