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. Teamcenter_NX集成开发:通过NXOpen查询零组件是否存在

    之前用过NXOpen PDM的命名空间下的类,现在记录一下通过PDM命名空间下的类查询Teamcenter零组件的信息,也可以用来判断该零组件是否存在. 1-该工程为DLL工程,直接在NX界面调用,所 ...

  2. 搞一个自己用的node-cli

    我们都用过 vue 的cli ,或者 react的cli,  亦或是其他的cli 如 vite 等.他们都是提供了一个全局命令,然后在终端执行这个全局命令就可以创建出模板项目.今天我们就自己做一个,给 ...

  3. 面试突击:MVCC 和间隙锁有什么区别?

    MVCC 和间隙锁是两种完全不同的机制,但它们的目的都是相同的,都是用来保证数据库并发访问的,我们先来看二者的定义. MVCC 定义 MVCC 是多版本并发控制(Multi-Version Concu ...

  4. uniapp H5图片编辑器(安卓/iOS适用)

    箭头绘制参考了:https://blog.csdn.net/qq_45939676/article/details/127425426 这位大佬的文章 gitee地址: https://gitee.c ...

  5. Oracle宕机之PMON (ospid: 248987): terminating the instance due to error 484(另附hugepage配置方法)

    数据库版本:11.2.0.4 RAC环境 操作系统版本:Asianux Server release 7.3 数据库报错分析 接到业务消息,应用无法访问,开发人员查看日志后发现无法连接数据库. 查看数 ...

  6. 华为人工智能atlasA800-9000物理服务器离线安装及CANN安装和MindSpore安装和Tensorflow安装

    目录 华为人工智能atlas A800-9000 物理服务器全程离线安装驱动以及CANN安装部署和MindSpore安装部署和Tensorflow安装部署 A800-9000 物理服务器安装驱动 使用 ...

  7. kubernetes核心实战(一)--- namespace

    kubernetes核心实战 1.资源创建方式 命令行创建 yaml文件创建 2.namespace 命名空间(namespace)是Kubernetes提供的组织机制,用于给集群中的任何对象组进行分 ...

  8. C++/Qt网络通讯模块设计与实现(总结)

    至此,C++/Qt网络通讯模块设计与实现已分析完毕,代码已应用于实际产品中. C++/Qt网络通讯模块设计与实现(一) 该章节从模块的功能需求以及非功能需求进行分析,即网络通讯模块负责网络数据包的发送 ...

  9. 【Vue原理模拟】模拟Vue实现响应式数据

    1. 预期效果 当数据变动时,触发自定义的回调函数. 2. 思路 对对象 object 的 setter 进行设置,使 setter 在赋值之后执行回调函数 callback(). 3.细节 3.1 ...

  10. [Android]ADB调试: SecurityException: Injecting to another application requires INJECT_EVENTS permission

    问题描述 使用ADB工具调试安卓设备时报此错误: C:\Users\Johnny>adb shell input text "Hello" java.lang.Securit ...