Electron 是如何工作的

news2024/12/24 3:05:25

1. 创建electron项目

pnpm init
pnpm add -D electron
  • 修改配置项

    • package.json
{
  "name": "electron-menu",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",	// eletron入口
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev":"electron ."	// 启动electron
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^32.1.2"
  }
}

2. 创建文件

  • main.js 项目入口文件
const { BrowserWindow, app} = require("electron");
const path = require("path");

const createWindow = () => {
    const win = new BrowserWindow({
        width: 300,
        height: 300,
        webPreferences: {
        	// 引入预加载文件
            preload: path.join(__dirname, "preload.js")
        }
    })
    // 窗口的页面
    win.loadFile(path.join(__dirname, "index.html"))
}
// 主进程启动完成后启动渲染进程
app.whenReady().then(() => {
    createWindow()
})
  • index.html 渲染进程的网页
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://web.nodejs.cn/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'"
    />
    <script src="./index.js"></script>
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    <h1>Hello from Electron renderer!</h1>
    <p>👋</p>
  </body>
</html>
  • index.js 网页的script部分
暂时无内容
  • preload.js 预加载文件
暂时无内容

3. electron的进程

1. 主进程

  • app:主进程。
    1. 主要作用创建和管理应用窗口BrowserWindow
    2. 主进程是运行在nodejs,可直接使用Node.js API对“操作系统”控制
const { app } = require("electron");

app.whenReady().then(() => {
	// 主进程开启后,开启一个渲染进程
    createWindow()
});
  • 主进程的窗口
const createWindow = ()=>{
    const win = new BrowserWindow({
    	// 视窗的宽/高
        width: 200,
        height: 200,
    });
    // 对窗口进行配置
}
  • 窗口的部分方法
    // 设置窗口的宽高比
    // 1:1,在示例中就是正圆
    win.setAspectRatio(1);
    
    // 使用path模块,来设置路径,不同的操作系统,路径的表示方式是不一样的
    // __dirname 表示当前文件所在的目录
    // 加载文件
    win.loadFile(path.join(__dirname,"index.html"));
    
    // 加载链接
    win.loadURL("https://www.baidu.com")

    // 打开开发者工具
    win.webContents.openDevTools();

2. 渲染进程

1. 预加载脚本: 负责进程间通信

  • 主进程和渲染进程的通信通道
  • 可使用部分nodejs的api,在渲染进程中执行
  • 在预加载脚本中可以直接通过window的属性操作dom,但是最好放在渲染进程中操作dom

在窗口设置预加载脚本文件
在渲染进程渲染窗口前加载,是渲染进程与主进程通信的桥梁

    const win = new BrowserWindow({
        width: 200,
        height: 200,
        // 窗口功能的设置
        webPreferences: {
            // 预加载脚本
            preload: path.join(__dirname,"preload.js"),
        },
    });

2. 负责html页面在窗口的渲染

  • HTML 文件是渲染器进程的入口点。
  • UI 样式是通过级联样式表 (CSS) 添加的。
  • 可以通过 <script> 元素添加可执行的 JavaScript 代码。

3. 安全机制

1. 设置预加载脚本使用nodejs的全部api

  • 不开启的话,预加载脚本没有权限做的事情,需要主进程去做
    const win = new BrowserWindow({
        width: 200,
        height: 200,
        // 网页功能的设置
        webPreferences: {
            // 预加载脚本
            preload: path.join(__dirname,"preload.js"),
            // 开启node环境
            nodeIntegration: true,
            //contextIsolation=true
        },
    });

2. 安全设置

node集成:nodeIntegration默认值为false
上下文隔离:contextIsolation默认值为true

  1. contextIsolation
    网页的scripit与预加载文件是否可以直接共享数据
    • contextIsolation=true:不共享数据
    • contextIsolation=false:共享数据
  2. nodeIntegration
    • nodeIntegration:false预加载文件不能直接使用nodejs
    • nodeIntegration:true
      预加载文件可以直接使用nodejs,如果同时contextIsolation=false,网页与预加载文件共享数据,导致网页也可以直接使用nodejs

3. 预加载脚本和页面的数据共享

contextIsolation=true

  • 在预加载脚本中和页面中的window是同一个对象,但是他俩定义的数据并没有共享
    - 在预加载脚本中定义window.abc= 'aaabbb',在页面的window无法访问abc属性

4. 通信

预加载脚本最主要的功能:主进程与页面通信的桥梁

1. 主进程与预加载脚本订阅方法通信

  1. 主进程中的订阅和发送(与预加载脚本的通信)
// 订阅消息
ipcMain.on("load-data",(event, ...args)=>{
	// 通过event.sender获取到发送者,向发送者返回信息
    event.sender.send("response",...args)
})
  1. 预加载脚本的订阅和发送(与主进程通信)
  • 发送消息
ipcRenderer.send("load-data",...args);
  • 订阅消息
ipcRenderer.on("response",(event, ...args)=>{
	// todo
});

2. 预加载脚本与html中的script通信

1. 预加载脚本

  • contextBridge.exposeInMainWorldkeyhtml中的script共享
contextBridge.exposeInMainWorld("electronAPI", {
	a:123,
	b:function(callback){
		// todo
		ipcRenderer.send("load-data",...args1);
		ipcRenderer.on("response",(event, ...args2)=>{
		// 通过callback将参数传给 html中的scrip
		// 这样就能将主进程的数据发往渲染进程
		callback(args2)
});
	}
});

2. html中的script

  • 通过window.[共享的key],访问预加载脚本中的数据
window.addEventListener("DOMContentLoaded", () => {
	// 对dom的所有操作都放在这个文件
   window.electronAPI.a
   window.electronAPI.b(function(arg2){
	// todo
	})
}) 

3. 订阅通信方式示例: 自定义菜单

  1. 创建一个菜单文件
const { Menu } = require('electron')

const createMenu = (win) => {
    const menu = [
        {
            label:'菜单',
            submenu:[
                {
                    label:'增加',
                    click(){
                    	// 订阅事件发送
                        win.webContents.send("add-item")
                    }
                }
            ]
        }
    ]
    
    Menu.setApplicationMenu(Menu.buildFromTemplate(menu))
}

module.exports = {
    createMenu
}
  1. 在窗口中使用菜单
const createWindow = () => {
    const win = new BrowserWindow({
        width: 300,
        height: 300,
        webPreferences: {
            preload: path.join(__dirname, "preload.js")
        }
    })
    win.loadFile(path.join(__dirname, "index.html"))
    // 菜单按钮
    createMenu(win)
}

  1. 在预加载脚本中监听订阅事件
contextBridge.exposeInMainWorld('electronAPI', {
    counter:(callback) => {
        ipcRenderer.on('add-item',()=>{
            callback()
        })
    }
})
  1. 页面的script
window.addEventListener("DOMContentLoaded", () => {
    window.electronAPI.counter(()=>{
       const dom = document.getElementById('counter')
       dom.innerText = parseInt(dom.innerText) + 1
    })
})  

在这里插入图片描述

4. 订阅通信方式示例:在页面中显示版本号

  • html
<div id="chrome"></div>
<div id="electron"></div>
<div id="node"></div>
  • 预加载文件
    • 不推荐在预加载文件中操作dom
    • 预加载文件中使用了nodejs的api,process
window.addEventListener("DOMContentLoaded", () => {
    for(let app of ['chrome','electron','node']){
        const el = document.querySelector(`#${app}`)
        el.innerHTML=`${app}:${process.versions[app]}`
    }
})

在这里插入图片描述

5. 主进程和预加载脚本,invoke.handle,请求-响应模式

  • 与on和send订阅的方式相比
    1. 在invoke和handle中使用return返回数据,更方便,适合有数据返回的情况
  1. html页面
    <button id="upload_btn">上传图片</button>
    <img id="img" src="" alt="">
  1. html页面的script
window.addEventListener("DOMContentLoaded", () => {
    const btn = document.getElementById("upload_btn")
    const img = document.getElementById("img")
    btn.addEventListener("click", async () => {
        img.src = await window.electronAPI.uploadImg()
    })
})
  1. 预加载脚本
    • 发送请求,并等待响应
    • 使用on和send的订阅方式时,需要使用callback给页面传递数据,现在只要使用return就能把数据传回给html页面的script
    • 一句代码即完成了给主进程发送消息的功能也完成了返回给页面数据的功能
contextBridge.exposeInMainWorld("electronAPI",{
    uploadImg:()=>{
        return ipcRenderer.invoke("upload-img")
    }
})
  1. 主进程
    • 接收请求并发送响应
    • 与订阅相比,只需要return就可以把数据发送到预加载脚本中
ipcMain.handle("upload-img",async (event)=>{
	// 使用dialog上传文件
    const obj = await dialog.showOpenDialog()
    return obj.filePaths[0]
})

在这里插入图片描述

5. Menu

  • 运行在主进程中
  • Menu的方法和属性官网链接
  • 菜单配置项的官网链接

1. 顶部菜单项

  1. 创建menu.js文件
    • role:electron已经定义好的菜单.官网链接
    • {type: "separator"},分隔符
    • accelerator: 'CommandOrControl+k'定义快捷键
const { app, Menu, shell } = require("electron");
const isMac = process.platform === "darwin";

const mainMenu = (win) => {
  const menu = [
    ...(isMac	// 是否为mac系统,mac系统与win的菜单不一样
      ? []
      : [
          {
            label: "File",
            submenu: [
              {
                label: "开发者工具",
                accelerator: 'CommandOrControl+k',   // 定义快捷键
                click: () => {
                  // 打开开发者工具
                  win.webContents.openDevTools({mode: "detach",activate:true,});
                  // 给渲染进程发送订阅事件
                  win.webContents.send("menu-subscribe");
                },
              },
              {
                label: "退出",
                click: () => {   // 自定义点击事件
                  app.quit();
                },
              },
              { type: "separator" },	// 分割线
              {
                label: "退出",
                role: "quit", // electron已经定义好的菜单
              },
            ],
          },
        ]),
  ];
  Menu.setApplicationMenu(Menu.buildFromTemplate(menu));
};
module.exports = mainMenu;
  1. 给menu传递窗口
const { app } = require('electron')
const mainWindow = require('./main-window.js')
const  mainMenu  = require('./menu')

app.whenReady().then(()=>{
    const win = mainWindow()
    // 传递窗口对象给菜单模块
    mainMenu(win)
})

2. 右键菜单

  1. 鼠标的右键点击事件
const { ipcRenderer } = require("electron");

// 窗口右键点击事件
window.addEventListener("contextmenu", function (event) {
  ipcRenderer.send("context-menu-event");
});
  1. 在主程序中订阅事件
const { ipcMain,Menu,BrowserWindow } = require("electron");

ipcMain.on("context-menu-event", (event, args) => {
  // 右键菜单
  const menu = [
    {
      label: "Menu Item 1",
      click: () => {},
    },
    { type: "separator" },
    { label: "Menu Item 2", type: "checkbox", checked: true },
  ];
Menu.buildFromTemplate(menu).popup(BrowserWindow.fromWebContents(event.sender));
});

6. dialog

  • 运行在主进程中
  • 官网链接

1. dialog.showMessageBox 显示提示信息

  • 是electron的弹出框,不是网页的弹出框,所以要放在menu中使用
const { dialog, Menu } = require("electron");
const createMenu = (win) => {
  const mainDialog = [
    {
      label: "文件",
      submenu: [
        {
          label: "新建",
          click: async () => {
            const res = await dialog.showMessageBox(win,{	// 参数win可以省略,这里是为了当主窗口设置置顶后,dialog还可以出现在窗口的上面
              title: "文件管理", // 标题
              detail: "确定要新建文件吗?", // 提示信息
              buttons: ["确定", "取消"], // 按钮的顺序
              cancelId: 1, // 取消按钮的数组索引
            });
            console.log(res);
            // 点击确定按钮:{ response: 0, checkboxChecked: false }
            // 点击取消按钮:{ response: 1, checkboxChecked: false }
          },
        },
      ],
    },
  ];
  Menu.setApplicationMenu(Menu.buildFromTemplate(mainDialog));
};
module.exports = createMenu;

在这里插入图片描述

  • 需要注意的配置项:
    • 通过 Esc 键取消对话框,cancelId的值就是 Esc 键的目标
    • 数组的顺序决定了按钮在对话框中位置
  1. cancelId:1,指明了取消按钮在数组中的索引为1
buttons: ["确定", "取消"], // 按钮的顺序
cancelId: 1, // 取消按钮的数组索引
  1. cancelId的默认值为0,则需要在数组中将取消按钮排到索引0的位置,这样就保证Esc键可以取消对话框
buttons: ["取消", "确定"], // 按钮的顺序
// cancelId: 1,

在这里插入图片描述

2. 复选框

        {
          label: "删除文件",
          click: async () => {
            const res = await dialog.showMessageBox(win, {
              title: "文件管理", // 标题
              detail: "确定要删除文件吗?", // 提示信息
              buttons: ["取消", "确定"], // 按钮的顺序
              checkboxLabel: "确定要删除文件?",    // 复选框
              checkboxChecked: false,   // 复选框默认选中状态
            });
            console.log(res);
            // 点击确定按钮:{ response: 0, checkboxChecked: false }
            // 点击取消按钮:{ response: 1, checkboxChecked: false }
          },
        },

在这里插入图片描述

  • 返回的结果:

    通过对返回结果中两个属性值来判断下一步要做什么

    • response: 0, 点击按钮对应的buttons数组的索引
    • checkboxChecked: false 点击确认或者取消后返回复选框的状态

3. dialog.showOpenDialog 打开文件

官网链接

  • 参考4.5:通信的请求响应模式
  const res = await dialog.showOpenDialog({
    title: "选择图片文件",
    properties: ["openFile", "multiSelections"],	// multiSelections:打开多个文件
    filters: [{ name: "image", extensions: ["jpg", "png"] }],
  });

res的结果,如果是多选,路径组成数组filePaths

    {
    canceled: false,
    filePaths: [ 'G:\\my doc\\图片\\测试用的图片\\c9b1eff4fc8e86abeab663cc7a9e7c30.jpg' ]
    }

4. dialog.showSaveDialog 保存文件

  • properties:保存文件时,可以新建文件夹
ipcMain.handle("save-file",async (event,value)=>{
    const res = await dialog.showSaveDialog({properties:"createDirectory"})
    // res:{canceled:false, filePath:'文件的保存地址'}
    if(!res.canceled){  // 用户是否点击了取消
        try{
            fs.writeFileSync(res.filePath,value)
            // fs方法没有返回值,保存失败会抛出错误信息
            return true
        }catch(error){
            alert("文件保存失败,"+ error)
        } 
    }
    return false
})

7. webContents

1. 在浏览器打开<a>的新窗口

<a href="https://www.baidu.com" target="_blank">百度</a>
<--! target="_blank" 在新窗口打开 -->

点击a标签,结果会打开一个新的electron窗口,而不是在浏览器中打开

const createWindow = ()=>{
    const win = new BrowserWindow({
        width:800,
        height:600,
        alwaysOnTop:true,
        webPreferences:{
            preload: path.join(__dirname,'preload.js')
        }
    })
    win.loadFile(path.join(__dirname,'index.html'))
    win.webContents.openDevTools()
    // 设置setWindowOpenHandler后就可以在浏览器打开新窗口
    win.webContents.setWindowOpenHandler((detail)=>{
        shell.openExternal(detail.url)
    })
    return win
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2184776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【重学 MySQL】四十七、表的操作技巧——修改、重命名、删除与清空

【重学 MySQL】四十七、表的操作技巧——修改、重命名、删除与清空 修改表添加字段语法示例注意事项 删除字段语法示例 修改字段使用 MODIFY COLUMN语法示例 使用 CHANGE COLUMN语法示例 重命名表语法示例 删除表语法示例 清空表使用 TRUNCATE TABLE使用 DELETE FROM对比 TRUNC…

聊聊晶圆厂中的常见口语(1)

知识星球里的学员问&#xff1a;半导体公司的工程师总爱用一些英语代替中文&#xff0c;比如care,show&#xff0c;用这种简单的单词代替中文&#xff0c;能不能给我们总结工程师常用的英语单词&#xff0c;比较口语化的&#xff01; 为什么晶圆厂会用很多英文口语&#xff1f…

华为---以太网静态路由配置使用下一跳通信正常,而使用出接口无法通信

目录 1. 实验环境 2. 结果测试 3. 分析验证 3.1 以太网静态路由配置使用下一跳跨网段通信抓包分析 3.2 以太网静态路由配置使用出接口跨网段通信抓包分析 3.3 以太网静态路由配置使用出接口无法跨网段通信问题解决办法 1. 实验环境 以太网静态路由配置使用下一跳跨网段通…

番茄成熟度检测系统源码分享

番茄成熟度检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

Opencv第十一章——视频处理

1. 读取并显示摄像头视频 1.1 VideoCapture类 VideoCapture类提供了构造方法VideoCapture(),用于完成摄像头的初始化工作&#xff0c;其语法格式如下&#xff1a; capture cv2.VideoCapture(index) 参数说明&#xff1a; capture:要打开的摄像头视频。 index:摄像头设备索引。…

【区间dp】AT_dp_l 题解

题意 给一个双端队列&#xff0c;双方轮流取数&#xff0c;每一次能且只能从队头或队尾取数&#xff0c;取完数后将这个数从队列中弹出。双方都希望自己取的所有数之和尽量大&#xff0c;且双方都以最优策略行动&#xff0c;假设先手取的所有数之和为 X X X&#xff0c;后手取…

【Git】一文看懂Git

Git 一、简介1. Git 与 SVN 区别1.1 Git 是分布式的&#xff0c;SVN 不是1.1.1 分布式版本控制系统Git1.1.2 集中式版本控制系统SVN 1.2 Git 把内容按元数据方式存储&#xff0c;而 SVN 是按文件1.3 Git 分支和 SVN 的分支不同1.4 Git 没有一个全局的版本号&#xff0c;而 SVN …

五.运输层

目录 5.1概述 5.2传输层的寻址与端口 熟知端口号 套接字(Socket) 5.3 UDP 特点 UDP报文格式 UDP校验 二进制反码求和 5.4 TCP 特点 可靠传输 停止等待协议 流水线方式 累计应答 流量控制 滑动窗口 拥塞控制 三次握手&#xff0c;四次握手 5.1概述 只有主机…

Pikachu-Cross-Site Scripting-反射型xss(get)

存储型XSS 存储型XSS是指恶意脚本被存储在目标服务器上&#xff0c;当用户访问包含该脚本的页面时&#xff0c;脚本会被执行。攻击者通常通过输入框、留言板等用户可输入的地方进行注入。例如&#xff0c;攻击者可以在留言板中输入恶意脚本&#xff0c;当其他用户查看留言时&a…

3.基于分数的生成模型

1.简介 基于分数的生成模型(SGM)的核心是Stein分数(或分数函数)。给定一个概率密度函数p(x)&#xff0c;其分数函数定义为对数概率密度的梯度Vxlogp(x)。生成模型通过学习并建模输入数据的分布&#xff0c;从而采集生成新的样木&#xff0c;该模型广泛运用于图片视频生成、文本…

假期惊喜,收到公司款项86167.14元

假期惊喜 近日&#xff0c;有网友爆料称&#xff0c;比亚迪在未提前通知员工的情况下&#xff0c;突然发放了利润奖金。 有人获得了七八万元&#xff0c;也有人拿到了十多万元。 一位比亚迪员工的帖子显示&#xff0c;在9月26日下午&#xff0c;他的银行卡突然收到一笔 86167.1…

数字化那点事:一文读懂数字孪生

一、数字孪生的定义 数字孪生&#xff08;Digital Twin&#xff09;是指通过数字技术构建的物理实体的虚拟模型&#xff0c;能够对该实体进行全方位、动态跟踪和仿真预测。简单来说&#xff0c;数字孪生就是在一个设备或系统的基础上创造一个数字版的“克隆体”&#xff0c;这…

Redis --- 第二讲 --- 特性和安装

一、背景知识 Redis特性&#xff1a; Redis是一个在内存中存储数据的中间件&#xff0c;用于作为数据库&#xff0c;作为缓存&#xff0c;在分布式系统中能够大展拳脚。Redis的一些特性造就了现在的Redis。 在内存中存储数据&#xff0c;通过一系列的数据结构。MySQL主要是通…

Ollama安装部署CodeGeeX4 - ALL - 9B

一、模型本地部署准备 1、 conda create -n ollama python3.82、 curl -fsSL https://ollama.com/install.sh | sh3、验证安装 安装完成后&#xff0c;通过运行以下命令来验证Ollama是否正确安装&#xff1a; ollama --version4、启动ollama ollama serve模型地址&#xff…

【重学 MySQL】四十八、DCL 中的 commit 和 rollback

【重学 MySQL】四十八、DCL 中的 commit 和 rollback commit的定义与作用rollback的定义与作用使用场景相关示例注意事项DDL 和 DML 的说明 在MySQL中&#xff0c;DCL&#xff08;Data Control Language&#xff0c;数据控制语言&#xff09;用于管理数据库用户和控制数据的访问…

Ubuntu 安装RUST

官方给的是这样如下脚本 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh 太慢了 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh -x 执行这个脚本后会给出对应的下载链接 如下图 我直接给出来 大多数应该都是这个 https://static.rust-…

初识算法 · 双指针(1)

目录 前言&#xff1a; 双指针算法 题目一&#xff1a; ​编辑 题目二: 前言&#xff1a; 本文作为算法部分的第一篇文章&#xff0c;自然是少不了简单叭叭两句&#xff0c;对于算法部分&#xff0c;多刷是少不了&#xff0c;我们刷题从暴力过度到算法解法&#xff0c;自…

csp-j模拟二补题报告

目录传送门 前言第一题下棋&#xff08;chess&#xff09;我的代码&#xff08;AC了&#xff09;AC代码 第二题汪洋&#xff08;BigWater&#xff09;我的代码&#xff08;0&#xff09;AC代码 第三题删数&#xff08;delnum&#xff09;我的代码&#xff08;0&#xff09;AC代…

秋招突击——9/13——携程提前准备和实际面经——专程飞过去线下,结果一面挂(难受)

文章目录 引言面经收集面经整理一1. ArrayList和LinkedList2. 线程安全的列表和链表有么&#xff1f;如果没有怎么实现&#xff1f;3. threadlocal4. synchronized锁升级过程及原理5. ReentrantLock原理&#xff0c;以及和synchronized的对比6. 线程池工作原理7. redis常用数据…

数据流和数据流处理技术

一数据流 首先明确数据流概念&#xff1a;数据流是连续不断生成的、快速变化的无界数据序列 数据流类型&#xff1a; 数据流大致可以分为四种类型 1.连续型数据流&#xff1a;不断地产生数据&#xff0c;数据稳定速度输入系统。 2.突发型数据流&#xff1a;在某特定时间或…