electron

作者: 何亮hook_8285 | 来源:发表于2023-12-26 00:16 被阅读0次

    主进程和渲染进程

    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
    

    相关文章

      网友评论

          本文标题:electron

          本文链接:https://www.haomeiwen.com/subject/pdvpgdtx.html