欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

electron

程序员文章站 2022-06-04 19:35:39
...

node GUI

1. NW.js和Electron都是基于NodeJs的GUI框架,使用HTML、CSS、JS来构建UI,处理与用户的交互
2. NW.js和Electron专注于开发桌面跨平台应用,而不是web服务器
3. NW.js的原称为Node-Webkit,目前由因特尔公司维护,而Electron由Github维护;
4. NW.js和Electron都使用了开源浏览器Chromium,并使用NodeJs访问系统、文件、网络...

Electron

1. Electron的原理:将Chromium和Node.js整合到一个运行时环境中,且可以跨平台打包;
    1. Chromium是一个谷歌开源浏览器,可以调用所有前端相关的API,且不需要考虑兼容性问题;
    2. 浏览器并不能访问原生的资源,但Node.Js的API支持在页面中和操作系统进行交互。
2. npm install -g electron:全局安装Electron
    1. 可以在Github上下载Electron的demo:electron-quick-start
    2. 进入demo目录,执行 cnpm install 安装依赖,执行 npm start 运行demo
3. electron-forge:相当于Electron的脚手架,可以快速创建、运行、打包项目;
    1. npm install -g electron-forge:全局安装
    2. electron-forge init myDemo:创建项目,默认使用 npm i 安装依赖;
    3. 如果执行失败,可以删除项目中的node_modules,执行 cnpm i,手动安装依赖;
4. 手动搭建项目:创建目录,在目录中创建main.js和index.html,执行cnpm init;
    1. main.js就是主进程,项目运行的入口文件;
    2. 一个应用只有一个主进程,但可以有多个渲染进程,一个窗口就表示一个渲染进程;
    3. electron .:运行项目。
5. 拖放打开文件
    <textarea id="container"></textarea>
    1. 调用H5的拖动API,获取拖放文件的路径
    let box = document.querySelector('#container')
    box.ondragenter=box.ondragover=box.ondragleave = (e) => {
        return false; --> 组织默认行为
    }
    box.ondrop=(e) => {
        e.preventDefault(); --> 阻止默认行为
        let path = e.dataTransfer.files[0].path; --> 获取文件的路径
    }
    2. 使用Node.js的fs模块读取文件,赋值给box.value:let fs = require('fs')
6. 相关模块
    1. Electron中的模块分为:主进程模块,渲染进程模块,公用模块;
    2. app:主进程模块,控制应用的生命周期,写法比较固定,注册固定的几个事件;
    3. BrowserWindow:主进程模块,窗口相关的模块;
    let win = BrowserWindow.getFocusedWindow();  -->获取当前窗口的对象
    win.center();  -->把窗口移动到屏幕*
    4. remote:渲染进程的模块,用于间接调用部分主进程的模块,如BrowserWindow
    5. Menu:主进程的菜单模块,包括顶部菜单、右键菜单、系统托盘的右键菜单。
7. Menu菜单功能必须在窗口加载完成之后才能执行;
    app.on('ready', {
        ...... //创建窗口BrowserWindow
        require('./main/menu.js') //加载创建菜单的js模块
    })
    1. 虽然渲染进程也能通过remote模块调用Menu模块,实现自定义的顶部菜单,但可能会闪现默认
    的顶部菜单,所以还是建议在主进程中实现顶部菜单。

进程间通信IPC

1. 被Electron直接运行的脚本(package.json中指定的main节点)称为主进程,有且只有一个;
2. 用于展示的web界面都运行在一个独立的进程中,称为渲染进程;
3. 进程间通信的相关模块:ipcMain(主进程)、ipcRenderer(渲染进程)

主进程与渲染进程

1. 渲染进程向主进程发送异步消息:ipcRenderer.send('事件名1', 消息内容)
    1. 主进程中监听消息:ipcMain.on('事件名1', (event, arg) => { ... })
    2. 主进程给渲染进程回复消息:event.sender.send('事件名2', 消息内容)
    3. 渲染进程监听回复的消息:ipcRenderer.on('事件名2', (event, arg) => {})
2. 渲染进程向主进程发送同步消息:
    1. let msg = ipcRenderer.sendSync('事件名1', 消息内容)
    2. 与异步消息不同,主进程收到同步消息,必须回复,否则渲染进程会阻塞;
    ipcMain.on('事件名1', (event, arg) => {
        event.returnValue = 'sync reply' ---> 回复给渲染进程
    }) ----> sendSync()的返回值就是主进程回复的消息内容
3. 主进程把数据挂载到node的全局对象global上,渲染进程通过 getGlobal() 获取数据
    1. 主进程:global.username = 'abc';
    2. 渲染进程:remote.getGlobal('username');
    3. 这种方式污染了全局对象global,并不建议使用。

渲染进程与渲染进程

1. 渲染进程无法直接通信,最简单的方式是通过localStorage实现;
2. 通过BrowserWindow和webContents模块实现:
    1. webContents是一个事件发出者,负责渲染并控制网页,也是BrowserWindow的属性;
3. 渲染进程1向主进程发送消息,主进程创建渲染进程2,再向渲染进程2发送消息;
    ipcMain.on('事件名1', (event, arg) => { --> 主进程监听消息事件
        win = new BrowserWindow({width:400, height:300})
        win.loadURL(path.join('file:', __dirname, '../news.html'))
        win.webContents.on('did-finish-load', () => { --> 窗口加载完毕
            win.webContents.send('事件名2', arg) --> 向渲染进程2发送事件
        }) ---> 新的渲染进程中注册'事件名2'
    })
4. 渲染进程2向渲染进程1回复消息,则需要把渲染进程1的Id也发送给渲染进程2
    ipcMain.on('事件名1', (event, arg) => {
        //必须在新窗口加载之前获取当前窗口的Id,否则获取的新窗口的Id
        let winId = BrowserWindow.getFocusedWindow().id
        win = new BrowserWindow({width:400, height:300})
        win.loadURL(path.join('file:', __dirname, '../news.html'))
        win.webContents.on('did-finish-load', () => {
            win.webContents.send('事件名2', arg, winId)
        })
    })
5. 在新窗口中注册事件,向渲染进程1回复消息,在渲染进程1中监听回复事件
    ipcRenderer.on('事件名2', (event, arg, winId) => {
        let win = BrowserWindow.fromId(winId) -->获取渲染进程1的窗口对象
        win.webContents.send('事件名3', 'reply the msg') -->回复消息
    })

shell

1. shell模块可以操作用户的默认浏览器,调用资源管理器;
2. 调用用户的默认浏览器,打开指定网址:shell.openExternal('https://...')
3. shell.showItemInFolder()/openItem():资源管理器的相关操作;
4. <webview>:与HTML的<iframe>相似,但webview与应用程序不在同一个进程;
    1. webview没有渲染进程的权限,并且与应用之间的交互都是异步的,保证应用的安全性不受嵌入
    内容的影响;
    2. <webview src="https://www.baidu.com"></webview>

dialog

1. dialog模块:不仅可以弹出信息提示框,也可以实现本地文件的打开与保存;
2. 信息提示框
    1. dialog.showErrorBox('title', 'content'):错误信息提示框;
    2. dialog.showMessageBox({}, (index)=>{...}):信息提示框;
    3. 异步变同步:let index = dialog.showMessageBox({})
3. 打开文件/目录:dialog.showOpenDialog({}, (path) => {...})
    1. 打开功能只是回调文件/目录的路径,并不能获取文件/目录的内容;
    2. 操作文件/目录的功能,需要通过nodejs实现。
4. 保存文件:dialog.showSaveDialog({}, (path) => {...})
    1. showSaveDialog()只回调保存路径,并不是真的保存,不会在本地生成任何文件;
    2. 保存功能需要借助nodejs实现,通过fs模块把内容写入本地。
5. 打印当前窗口:BrowserWindow.getFocusedWindow().webContents.print()
6. app.quit():退出应用,Electron使用的是单例模式,此时不必考虑性能问题。

系统托盘

1. Tray模块:主进程的系统托盘模块;
2. 实现系统托盘:let tray = new Tray(path.join(__dirname, 'icon.png'))
3. 设置托盘名称:tray.setToolTip('title'),鼠标悬停在托盘图标上弹出;
4. 创建托盘的右键菜单
    1. Menu模块创建菜单:let menu = Menu.buildFromTemplate([{...}, {...}])
    2. 设置托盘的右键菜单:tray.setContextMenu(menu)
5. 关闭窗口,则隐藏到系统托盘;双击系统托盘,则重新打开窗口
    1. 关闭窗口
    let win = BrowserWindow.getFocusedWindow() --> 获取当前的窗口对象
    win.on('close', (e) => { --> 监听关闭按钮的事件
        if(win.isFocused()) { -->点击托盘右键菜单上的'退出',则关闭窗口
            win = null
        } else {
            e.preventDefault() --> 阻止默认的关闭事件
            win.hide() --> 隐藏窗口到系统托盘
        }
    })
    2. app.quit()会回调'close'事件,所以需要判断当前关闭的窗口是否拥有焦点;
    3. 如果当前窗口拥有焦点,则隐藏窗口;如果没有焦点,表示窗口已经处于隐藏状态,则关闭窗口
    4. 打开窗口
    tray.on('double-click', () => { --> 监听系统托盘的双击事件
        win.show()
    })
6. 托盘图标的闪烁:设置定时器setInterval(),每隔一段时间就重新设置一次图标。

消息通知

1. Electron的消息通知是通过H5的API实现的,右下方弹出通知框的效果;
    let option = {
        title: '通知标题', body: '通知内容', icon: '通知图标的路径'
    }
    let notification = new window.Notification(option.title, option)
    notification.onclick = ()=>{...} --> 通知框的点击事件
2. 监听网络变化:也是通过H5实现
    1. 网络正常:window.addEventListener('online', () => { ... })
    2. 网络断开:window.addEventListener('offline', () => { ... })

其他模块

1. globalShortcut模块:主进程模块,注册全局快捷键,在窗口创建之前注册;
    app.on('ready', () => {
        ... //注册全局快捷键 ---> ... //创建窗口 ---> ... //设置菜单
    })
    1. 注册全局快捷键:globalShortcut.register('ctrl+e', () => { ... })
    2. 判断是否注册成功:globalShortcut.isRegistered('ctrl+e')
    3. 窗口关闭时要取消全局快捷键:globalShortcut.unregister('ctrl+e')
    app.on('will-quit', () => { globalShortcut.unregister('ctrl+e') })
2. clipboard模块:公用模块,剪切板事件
    1. 复制操作:clipboard.writeText('复制内容'),系统的Ctrl+C
    2. 手动获取复制的内容:let text = clipboard.readText(),系统的Ctrl+V
    3. 除了读取文本,剪切板还能读取HTML、Image、RTF...
3. nativeImage模块:公用模块,用于读取、传递图片
    1. nativeImage.createEmpty():创建一个空的nativeImage对象;
    2. nativeImage.createFromPath('路径'):根据图片路径,创建nativeImage对象
4. 剪切板无法直接读取图片,需要借助nativeImage模块;
    let image = nativeImage.createFromPath('static/back.jpg')
    clipboard.writeImage(image) --> 复制图片
    let data = clipboard.readImage() --> 读取复制的图片,返回nativeImage对象
    let src = data.toDataURL() --> 转为图片的Base64编码
    1. 创建一个<img />:let img = new Image()
    2. 设置src属性:img.src = src
    3. 插入到HTML中:document.body.appendChild(img)

Electron-Vue

1. Electron-Vue:Electron结合Vue开发单页面桌面应用;
    1. 安装环境:cnpm install electron -g、cnpm install vue-cli -g
    2. 创建项目:vue init simulatedgreg/electron-vue my-project
    3. 下载依赖:cd my-project ---> cnpm install
    4. 运行项目:npm run dev
2. 在main.js中默认引入了electron,在Vue组件中通过this.$electron能直接使用;
    1. 主进程模块:src/main/index.js
    2. 渲染进程模块:src/renderer目录下的所有Vue组件
3. 在Vue组件中使用Node.js的模块:const path = require('path');
4. 打包后的electron应用无法找到图片
    1. 对于static目录下的图片:<img :src="'./static/img/a.png'" />
    2. 对于电脑本地的图片,直接使用图片的绝对路径,另外还要关闭窗口的安全模式
    new BrowserWindow({ webPreferences: { webSecurity: false } });

窗口操作

1. frame:false:无边框化,去除顶部菜单、最小化、最大化和关闭;
    let win = new BrowserWindow({..., useContentSize:true, frame:false})
    1. 无边框化之后,窗口的可拖拽区也随之消失,需要手动设置拖拽区,鼠标可以拖动窗口;
    2. 设置拖拽区的CSS属性:-webkit-app-region: drag
    body{ -webkit-app-region: drag } --> 设置整个窗口为拖拽区
    3. 拖拽区内的按钮必须标记为no-draggable,否则将不可点击;
    button{ -webkit-app-region: no-drag; }
2. win.setMenu(null):只隐藏顶部菜单;
3. 最大化、最小化、隐藏、关闭窗口
    1. win.hide():隐藏窗口; win.isVisible():窗口是否可见; win.close():关闭窗口
    2. win.maximize():最大化窗口,如果窗口尚未显示,这也将会显示,但不会有焦点;
    3. win.unmaximize():取消窗口最大化;  win.isMaximized():判断窗口是否最大化;
    5. win.minimize():最小化窗口;  win.isMinimized():判断窗口是否最小化;
    6. win.restore():将窗口从最小化状态恢复到以前的状态。

NeDB

1. NeDB:Nodejs实现的一个NoSQL嵌入式数据库;
    1. 与Android上的Sqlite类似,都是嵌入式数据库,一般用于本地数据的持久化;
    2. 不同的是,Sqlite是MySql类型的,使用SQL语句操作,而NeDB的操作类似于MongoDB;
    3. 可以充当内存数据库,也可以用来实现本地存储,甚至可以在浏览器中使用。
2. 使用方式:
    1. 安装操作模块:cnpm install nedb --save
    2. 在src/renderer目录下创建datastore.js,import Datastore from 'nedb'
    import path from 'path'  import {remote} from 'electron'
    export default new Datasotre({
        autoload: true,
        filename: path.join(remote.app.getPath('userData'), '/data.db')
    })
    3. 设置NeDB保存在userData目录中,该目录空间专门为应用程序所保留,不会被篡改;
    4. 在main.js中,把数据库操作对象绑定到原型上:
    import db from './datastore'  --> Vue.prototype.$db = db