• Electron桌面应用开发基础


    Electron桌面应用开发

    Electron特点

    Electron 是一种基于 Chromium 和 Node.js 的开源框架,可以用于快速构建跨平台的桌面应用程序。与传统的桌面应用程序不同,Electron 应用程序使用 HTML、CSS 和 JavaScript 技术 栈来实现界面设计和业务逻辑,并且具有良好的跨平台性能和扩展性。

    跨平台性:Electron 可以在 Windows、Mac 和 Linux 等多个平台上运行。它通过使用 web 技术栈来实现界面设计和业务逻辑,从而实现了跨平台的一致性和可移植性。同时,由于 Electron 底层使用 Chromium 和 Node.js,也可以很方便地使用各种第三方库和插件。

    灵活性:Electron 提供了很多自定义选项和 API 接口,可以满足各种定制化需求。例如,可以自定义菜单、对话框和图标等界面元素,还可以访问系统文件和网络资源等底层功能。

    生态圈支持:Electron 在 GitHub 上拥有庞大的社区和生态圈,提供了很多开源项目和插件,可以快速开发出高质量的桌面应用程序。同时,Electron 也得到了很多知名公司和开发者的支持,如 Slack、GitHub Desktop、VS Code 等。

    性能问题:由于 Electron 应用程序需要同时运行 Chromium 和 Node.js,因此在启动速度、内存占用和性能优化等方面可能存在一些问题。但是,通过合理的代码设计和优化,可以很好地解决这些问题。

    总之,Electron 是一种灵活、可扩展、跨平台的桌面应用程序开发框架,具有良好的生态圈和社区支持。对于前端开发人员来说,它提供了一种全新的开发方式和编程思路,为构建高质量的桌面应用程序提供了更多的便利和选择。

    Electron技术架构

    地址:快速入门 | Electron

    • Chromium 支持最新特性的浏览器
    • Node.js Javascript运行时,可实现文件读写
    • Native APIS 提供统一的原生界面能力

    环境搭建

    1. Node 安装 (我的版本14.15.0)
    2. 项目初始化
      npm init -y
      // 安装Electron 
      npm i --save-dev electron
      // 创建main.js 并在package.json中设置为入口
      "main":"main.js"
      // 创建index.html 用来书写页面内容
      
    3. 初始化代码

      package.json

      {
        "name": "myElectron",
        "version": "1.0.0",
        "description": "",
        "main": "main.js",
        "scripts": {
          "start": "nodemon --watch main.js --exec npm run build",
          "build": "electron ."
        },
        "keywords": [],
        "author": "",
        "license": "ISC",
        "devDependencies": {
          "electron": "^24.4.0"
        },
        "dependencies": {
          "@electron/remote": "^2.0.9"
        }
      }
      

      main.js

      const { app, BrowserWindow } = require( 'electron' )
      // app 哪个控制应用程序的事件生命周期
      // BrowserWindow   创建和管理应用程序Windows 
      const createWindow = () => {
        let mainWin = new BrowserWindow( {
          width: 800,
          height: 600,
          show: false,
          backgroundColor: 'aqua',
      	} )
        mainWin.loadFile( 'index.html' )
        mainWin.on( 'ready-to-show', () => {
          mainWin.show()
        	} )
        mainWin.on( 'close', () => {
          mainWin = null
        	} )
      }
      app.on( 'ready', () => {
        createWindow()
      } )
      

    生命周期事件 // 执行顺序如下

    • ready: app初始化完成

    • dom-ready: 一个窗口中的文本加载完成

    • did-finsh-load: 导航完成时触发

    • closed:当窗口关闭时触发,此时应删除窗口引用

    • window-all-closed: 所有窗口关闭时触发

    • before-quit: 在关闭窗口之前触发

    • will-quit: 在窗口关闭并且应用退出时触发

    • quit: 当所有窗口被关闭时触发

    • 
       mainWin.webContents.on( 'did-finish-load', () => {
          console.log( '333-did-finish-load' );
        } )
        // 窗口中文本加载完成
        mainWin.webContents.on( 'dom-ready', () => {
          console.log( '222dom-ready' );
        } )
        // 主窗口关闭
        mainWin.on( 'closed', () => {
          console.log( '88888-事件发生' );
        } )
      
      app.on( 'window-all-closed', () => {
        console.log( '444-window-all-closed' );
        if ( process.platform !== 'darwin' ) app.quit()
      } )
      app.on( 'ready', () => {
        console.log( '111- app初始化完成' );
        createWindow()
      } )
      
      app.on( 'before-quit', () => {
        console.log( '555-before-quit' );
      } )
      app.on( 'will-quit', () => {
        console.log( '666-will-quit' );
      } )
      app.on( 'quit', () => {
        console.log( '777-quit' );
      } )
      

    窗口设置

    const mainWin = new BrowserWindow( {
        x: 100,  //x y 窗口开始位置
        y: 100,   
        show: false,
        width: 800,
        height: 600,
        minHeight: 400, // min max 最小最大宽高
        minWidth: 50,
        maxHeight: 1000,
        maxWidth: 1200,
        resizable: false, // 窗口是否可调整
        minimizable: true,
        maximizable: true,
        title: '桌面应用',
        frame: false,
        // autoHideMenuBar: true,
        webPreferences: {
          nodeIntegration: true,  // 运行渲染进程使用node
          enableRemoteModule: true,
          contextIsolation: false
        }
      } )
      require( '@electron/remote/main' ).initialize()
      require( "@electron/remote/main" ).enable( mainWin.webContents )
    

    窗口标题及环境

    • 标题配置

      1. 优先读取index.html中的title
      2. index.html中不设置得情况下 可以读取 new BrowserWindow 中配置的title
    • 图标修改
      icon

    • frame 是否显示默认导航菜单 + 标题

    • transparent 设置透明

    • autoHideMenuBar 是否隐藏 导航菜单

    • 点击打开新窗口
      备注: 在main中运行时是主进程 在index.html中运行时是渲染进程
      app BrowserWindow 都属于主进程得模块
      出于安全考虑 渲染进程中没有办法使用require 可在main中配置
      渲染进程不允许直接使用主进程模块 通过remote进行调用
      electron 12 之后就已经废除了 remote
      // 替换为:
      const { BrowserWindow } = require('@electron/remote')

    // 在主进程中:
    require('@electron/remote/main').initialize()

    自定义窗口实现

    1. 渲染进程中获取 主进程窗口实例
      let mainWin = remote.getCurrentWindow()
    2. 获取各个按钮
      var btnGroup = document.getElementsByClassName( 'btnGroup' )[0].getElementsByTagName( 'button' )
    3. 是否最大判断
      mainWin.isMaximized()
    4. 最大化
    mainWin.maximize()
    // 最大化还原
    mainWin.restore()
    
    1. 最小化
    mainWin.minimize()
    
     // 
    // // // //
    // 获取按钮组 var btnGroup = document.getElementsByClassName( 'btnGroup' )[0].getElementsByTagName( 'button' ) btnGroup[0].addEventListener( 'click', () => { if ( !mainWin.isMinimized() ) { mainWin.minimize() } } ) btnGroup[1].addEventListener( 'click', () => { console.log( '最大化', mainWin.isMaximized() ); if ( !mainWin.isMaximized() ) { // 判断窗口是否最大化 mainWin.maximize() // 最大化 } else { mainWin.restore() } } ) btnGroup[2].addEventListener( 'click', () => { mainWin.close() } )

    阻止窗口关闭

    window.onbeforeunload = function() {
    return false
    }

    动态创建菜单

    1. 准备模板
    let menuArr = [{label:'打开',type:'normal',role:'copy'}]
    
    1. 利用模板生成菜单
    let menu = Menu.buildFromTemplate( menuArr )
    
    1. 添加到应用
    Menu.setApplicationMenu( menu )
    
    let temp = [
      {
        label: 'send',
        click () {
          BrowserWindow.getFocusedWindow().webContents.send( 'msg2', '主进程发来消息' )
        }
      }
    ]
    
    let tem = Menu.buildFromTemplate( temp )
    Menu.setApplicationMenu( tem )
    
    
    // 
      // 
      // 
    
    1. 找到  Menu  MenuItem
    2. new Menu()  可以将  new MenuItem() 创建的菜单进行添加到菜单栏中
    
    let remote = require( '@electron/remote' )
    let Menu = remote.Menu
    let MenuItem = remote.MenuItem
    window.addEventListener( 'DOMContentLoaded', () => {
      // 获取按钮-- 自定义菜单
      let selfBtn = document.getElementById( 'selfMenu' )
      let inputVal = document.getElementById( 'inputText' )
      let addMenu = document.getElementById( 'addMenu' )
      let selfMenuItem = new Menu()
      selfBtn.addEventListener( 'click', () => {
        let menuFile = new MenuItem( { label: '文件', type: 'normal' } )
        let menuEdit = new MenuItem( { label: '编辑', type: 'normal' } )
        let menuSelf = new MenuItem( { label: '自定义菜单', submenu: selfMenuItem } )
        let menu = new Menu()
        menu.append( menuFile )
        menu.append( menuEdit )
        menu.append( menuSelf )
        Menu.setApplicationMenu( menu )
      } )
    
      addMenu.addEventListener( 'click', () => {
        let content = inputVal.value.trim()
        if ( content ) {
          selfMenuItem.append( new MenuItem( { label: content, type: 'normal' } ) )
          content = ''
        }
      } )
    })
    
    

    右击弹出菜单

    1. 创建菜单

    2. 监听contextmenu 事件 并阻止默认行为

    3. menu.popup({window:remote.getCurrentWindow()})

       // 右键菜单
        let rightMenu = [
          {
            label: 'Run Code',
            type: 'normal'
          },
          {
            label: '刷新',
            role: 'refresh'
          },
          {
            type: 'separator'
          },
          {
            label: '其他功能',
            click () {
              console.log( '其他功能已执行' );
            }
          }
        ]
        let menuRight = Menu.buildFromTemplate( rightMenu )
        window.addEventListener( 'contextmenu', ( e ) => {
          e.preventDefault()
          menuRight.popup( {
            window: remote.getCurrentWindow
          } )
        } )
      

    主进程与渲染进程通信

    1. ipcRender(on send) ipcMain( on ) 两个进程之间通信
    - ipcMain  内部  e.sender.send('xx',xxx)
    - ipcMain  内部接收 e.returnValue
    
    1. BrowserWindow.getFocusedWindow().webContents.send('mtp',来自于主进程的消息) // 依赖按钮之类的事件触发‘

    2. mainWin.contents.openDevtools() // 打开控制台

      // main.js
      let { app, BrowserWindow, ipcMain } = require( 'electron' )
      ipcMain.on( 'msg1', ( e, ev ) => {
        console.log( e, ev );
        e.sender.send( 'msg2', 666 )
        // BrowserWindow.getFocusedWindow().webContents.send( 'msg2', 666 )
      } )
      
      // index.js
      let remote = require( '@electron/remote' )
      let { ipcRenderer } = require( 'electron' )
      window.addEventListener( 'DOMContentLoaded', () => {
        console.log( 666 );
        ipcRenderer.send( 'msg1', '渲染进程发来贺电' )
        ipcRenderer.on( 'msg2', ( e, ev ) => {
          console.log( e, ev );
        } )
      } )
      
      

    localtorage通信

    1. 获取主窗口id
    - BrowserWindow  实例属性  id
    
    1. 子窗口设置ID
    - BrowserWindow.fromId(mainWin.id)
    
    1. 通信的时候存储 信息到localStorage中

    2. 新窗口打开时取值并使用

      // main.js
      ipcMain.on( 'msg', ( e, data ) => {
          if ( data ) {
            // 开启第二个窗口
            let sub2 = new BrowserWindow( {
              parent: BrowserWindow.fromId( mainId ),
              width: 300,
              height: 150,
              webPreferences: {
                nodeIntegration: true,
                enableRemoteModule: true,
                contextIsolation: false
              }
            } )
            sub2.loadFile( 'sub.html' )
            sub2.on( 'close', () => {
              sub2 = null
            } )
          }
        } )
        // 主进程接收窗口二的信息
        ipcMain.on( 'toMain', ( e, data ) => {
          // 将数据转发给index进程
          let mainOpen = BrowserWindow.fromId( mainId )
          mainOpen.webContents.send( 'win2', data )
        } )
      
      // index.js
      let remote = require( '@electron/remote' )
      let { ipcRenderer } = require( 'electron' )
      
      window.addEventListener( 'DOMContentLoaded', () => {
      
        // 获取打开窗口按钮
        let btnOpen = document.getElementById( 'openTwo' )
        btnOpen.addEventListener( 'click', () => {
          localStorage.setItem( 'name', '张三' )
          ipcRenderer.send( 'msg', true )
        } )
        ipcRenderer.on( 'win2', ( e, data ) => {
          console.log( 'index进程已经接收到', data );
        } )
      } )
      

    渲染进程之间通信

    1. 主进程 A 渲染 B渲染
    2. A给B发生数据 ipcRender.send
    3. B接收 ipcRender.on
    4. B给A发数据 先发送给 主进程 主进程再发送给 A
    5. 注意: 主进程发送时不能直接使用BrowsserWindow.getFocusedWindow().webContents.send('mtp',来自于主进程的消息)
      因为当前焦点窗口不一定时主进程窗口

    dialog模块

    • dialog模块: 主进程模块 在渲染进程中使用remote.diaog 调用对应触发得窗口api
    • 配置:
      remote.dialog.showOpenDialog( {
      defaultPath: __dirname,
      buttonLabel: '请选择',
      title: '高傲的狼',
      properties: ['openFile', 'multiSelections'],
      filters: [
      { name: '代码文件', extensions: ['js', 'html', 'json'] },
      { name: '图片文件', extensions: ['ico', 'jpeg', 'png'] },
      { name: '媒体文件', extensions: ['mp3', 'mp4', 'avi'] },
      ]
      } ).then( res => {
      console.log( res );
      } )

    shell 打开url或者路劲

    1. shell.openExternal(url) 默认浏览器打开
    2. shell.showItemInFolder() 在桌面引用中打开

    消息提示

    • option = {
      title:'高傲的狼',
      body:'你好哇,小家伙',
      icon:'./xxx.ico'
      }
    1. window.Notification(
    option.title,option
    

    )

    快捷键

    1. 注册
    globalShortcut('ctrl + q')  // 返回布尔值  true/false
    
    1. 判断是否注册过
    globalShortcut.isRegistered('ctrl + q')
    
    1. 注销快捷键
    - 时机 -- 在生命周期 will-quit中进行注销
    - globalShortcut.unregister('ctrl + q')
    

    剪切板模块

    1. clipboard 模块
    - writeText
    - readText
    
    1. 图片复制 粘贴
      1. let img = nativeImage.createFromPath( './女孩.jpg' )
        2. clipboard.writeImage( img )
    // 将剪贴版中图片写到DOM中
    3. let oimg = clipboard.readImage()
    4. let imgDom = new Image()
    5. imgDom.src = oimg.toDataURL()
    6. document.body.appendChild( imgDom )
    
  • 相关阅读:
    LeetCode 高频题目分类列表
    java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发
    React中的类组件和函数组件(详解)
    java计算机毕业设计ssm宠物店管理系统(源码+系统+mysql数据库+Lw文档)
    3天快速入门python机器学习(黑马xxx)
    KaiwuDB 受邀亮相 2023 中国国际“软博会”
    【ESP32 + Edge Impulse平台】模拟多传感器数据融合实验测试
    阿里前端智能化技术探索和未来思考
    git 将本地分支与远程master主分支合并
    攻防世界---misc---心仪的公司
  • 原文地址:https://www.cnblogs.com/rlwan/p/17469540.html