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()
 // <div class="btnGroup">
// <button>最小</button>
// <button>最大</button>
// <button>关闭</button>
// </div> // 获取按钮组
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 ) // <button id="selfMenu">自定义菜单</button>
// <input type="text" value="" id="inputText">
// <button id="addMenu">加入新增菜单</button> 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 )

Electron桌面应用开发基础的更多相关文章

  1. Mac 桌面软件开发基础问答

    1> Mac OS X平台下的桌面软件是由什么编程语言处理 答: 由Objective-C, swift编程语言处理 2> Mac OS X平台下的桌面软件是由什么框架构建 答: 由Coc ...

  2. spring boot + vue + element-ui全栈开发入门——基于Electron桌面应用开发

     前言 Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库. Electron通过将Chromium和Node.js合并到同一个运行时环 ...

  3. 中文代码示例之Electron桌面应用开发初体验

    参考: 打造你的第一个 Electron 应用 首先运行下面在目录下创建package.json: $ npm init 去掉了一些无关项后内容如下: { "name": &quo ...

  4. 中文代码示例之NW.js桌面应用开发初体验

    先看到了NW.js(应该是前身node-webkit的缩写? 觉得该起个更讲究的名字, 如果是NorthWest之意的话, logo(见下)里的指南针好像也没指着西北啊)和Electron的比较文章: ...

  5. electron-vue:Vue.js 开发 Electron 桌面应用

    相信很多同学都知道 Electron 可以帮助开发人员使用前端技术开发桌面客户端应用,今天介绍的 electron-vue 框架是一套基于 Vue.js 开发 Electron 桌面应用的脚手架,该项 ...

  6. nw.js桌面软件开发系列 第0.1节 HTML5和桌面软件开发的碰撞

    第0.1节 HTML5和桌面软件开发的碰撞 当我们谈论桌面软件开发技术的时候,你会想到什么?如果不对技术本身进行更为深入的探讨,在我的世界里,有这么多技术概念可以被罗列出来(请原谅我本质上是一个Win ...

  7. JavaEE开发基础

    1 JavaEE简介 Java平台有三个版本,分别是JavaSE(Java Platform, Standard Edition),JavaEE(Java Platform, Enterprise E ...

  8. Android 开发基础及环境配置

    2011年买了第一部安卓操作系统的手机,当时势头正盛的HTC不可思议(incredible),当时的想法就是想学习下智能手机开发,但是由于各种原因,客观上是公司的项目太忙了,忙于项目管理.团队建设.客 ...

  9. Web桌面应用框架3:Web桌面应用开发的N种Style

    研究Web桌面应用开发有一段时间了,总结了Web桌面应用开发的一些主流方式. 一.前端Style 这种方式的就是直接实现一个Web程序,再封装一个浏览器展示,相当粗暴和有效.著名的框架就是Electr ...

  10. [No0000138]软件开发基础知识

    1. 本文目的 本文目的在于,介绍软件开发的各种基础知识 以实现,看了之后,对于软件开发的很多领域的基础知识有所了解 如此在进行后续的真正的软件开发时,遇到各种细节知识,才会明白由来和背景知识 第 1 ...

随机推荐

  1. Nebius Welcome Round (Div. 1 + Div. 2) 题解 A - D

    Nebius Welcome Round (Div. 1 + Div. 2) 题解 A - D,这四题的整体难度不大,都是过题人数上千的题. 很久没打Codeforces了,在此记录一篇题解,开学了也 ...

  2. PyQt5学习 (3)--QWidget(下)

    层级关系.层级控制: 调整Z轴顺序 点击查看代码 label1 = QLabel(window) label1.setText("标签1") label1.resize(200, ...

  3. Kafka存储内幕详解

    1.概述 随着微服务和分布式计算的出现,Kafka已经成为各种主流平台系统架构中不可缺少的组成部分了.在本篇文章中,笔者将尝试为大家来解密Kafka的内部存储机制是如何运作的. 2.内容 在分布式系统 ...

  4. Sevlet规范:HttpServlet类 和 HttpServletRequest接口 源码解析

    Sevlet规范:HttpServlet类 和 HttpServletRequest接口 源码解析 每博一文案 命运总是不如人愿,但往往是在无数的痛苦总,在重重的矛盾和艰辛中,才是人成熟起来. 你,为 ...

  5. SpringCloud源码学习笔记3——Nacos服务注册源码分析

    系列文章目录和关于我 一丶基本概念&Nacos架构 1.为什么需要注册中心 实现服务治理.服务动态扩容,以及调用时能有负载均衡的效果. 如果我们将服务提供方的ip地址配置在服务消费方的配置文件 ...

  6. LeeCode哈希问题(一)

    LeeCode 242: 有效的字母异位词 题目描述: 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词.若 s 和 t 中每个字符出现的次数都相同,则称互为字母异位词. ...

  7. 写一个 Hello SpringBoot2 项目

    需求:向浏览发送/hello请求,并响应 Hello,Spring Boot 2 解决: 项目目录:controller层.Main启动项.pom.xml controller层:写好逻辑跳转,当浏览 ...

  8. day01-Redis入门

    Redis入门 1.初始Redis 1.1认识NoSQL SQL(关系型数据库) NoSQL(非关系型数据库) 数据结构 结构化(Structured) 非结构化 数据关联 关联的(Relationa ...

  9. 吃透SpringMVC面试八股文

    说说你对 SpringMVC 的理解 SpringMVC是一种基于 Java 的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring框架的一个模块. 它通过一套注解,让一个简单的Jav ...

  10. Azure DevOps(三)Azure Pipeline 自动化将程序包上传到 Azure Bolb Storage

    一,引言 结合前几篇文章,我们了解到 Azure Pipeline 完美的解决了持续集成,自动编译.同时也兼顾了 Sonarqube 作为代码扫描工具.接下来另外一个问题出现了,Azure DevOp ...