1、入门

electron

Electron 基于 Chromium 和 Node.js, 让你可以使用 HTML, CSS 和 JavaScript 构建应用。

中文官网:https://www.electronjs.org/zh/

常见的桌面GUI工具介绍

名称 语音 优点 缺点
QT C++ 跨平台、性能好、生态好 依赖多,程序包大
PyQT Python 底层集成度高、易上手 授权问题
WPF C# 类库丰富、扩展灵活 只支持Windows,程序包大
WinForm C# 性能好,组件丰富,易上手 只支持Windows,UI差
Swing Java 基于AWT,组件丰富 性能差,UI一般
NW.js JS 跨平台性好,界面美观 底层交互差、性能差,包大
Electron JS 相比NW发展更好 底层交互差、性能差,包大
CEF C++ 性能好,灵活集成,UI美观 占用资源多,包大

环境

必须安装node、npm

Demo

创建应用程序

1、创建脚手架

mkdir demo && cd demo
npm init

执行完npm init后,会提示输入信息,除了entry point这个选项必须为main.js外,其余随意

2、安装electron包

由于网络原因,安装electron时可能会卡住,需要在项目根路径创建.npmrc文件,并写入镜像信息

electron_mirror = https://npmmirror.com/mirrors/electron/

或者执行命令:export electron_mirror = https://npmmirror.com/mirrors/electron/

然后再安装

npm install --save-dev electron

由于electron本身没有热启动的功能,所以需要引入依赖来实现

npm install --save-dev nodemon

3、配置启动命令

修改package.jsonmain标签,启动文件为main.js

package.jsonscripts标签下,添加"start": "electron .",开发环境使用npm start打开应用

{
  	"main": "main.js",
    "scripts": {
        "start": "electron .",
        "warm_start": "nodemon --exec electron . . --watch ./ --ext .js,.html,.css,.vue" //热启动
    }
}

4、创建页面

<!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>Hello World!</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        We are using Node.js <span id="node-version"></span>,
        Chromium <span id="chrome-version"></span>,
        and Electron <span id="electron-version"></span>.
    </body>
</html>

5、编写main.js

/** 此处需要引入两个模块
  * app 模块,它控制应用程序的事件生命周期。
  * BrowserWindow 模块,它创建和管理应用程序窗口。
  */
const {app,BrowserWindow} = require('electron')

function creatWindow(){
    win = new BrowserWindow({
        width: 1200,
        height: 800
    })
    //加载html文件
    win.loadFile("index.html")
    //加载链接
    win.loadURL("https://www.ygang.top")
    //开启开发者工具
    win.webContents.openDevTools();
}

app.whenReady().then(() => {
    creatWindow()
})

此时,使用npm start命令,可以打开窗口啦

程序运行在Web环境,可以按照正常的前端写法来写,index.html中,不需要引入main.js,其余的js正常引入使用就可以

打包

Package

a、安装打包工具
cnpm install -g electron-packager
b、打包命令
electron-packager ./ 'HelloWorld' --platform=win32 --arch=x64 --icon=icon.ico --out=./out --asar --app-version=0.0.1 --overwrite --electron-version 16.0.1

参数说明:

./:被打包的项目路径,当前目录

'HelloWorld':打包后的项目名

--platform:打包哪个平台的应用,可选win32linuxdarwinmas,这个也可以换为--all,打包全平台

--arck:系统位数,x86,x64

--icon:应用图标

--out:打包文件输出目录

--app-version:应用版本

-asar:可以不加,影响打包后源码格式

--overwrite:是否覆盖原来打包的

--electron-version:Electron版本

可以将打包命令,配置在package.json文件scripts中

{
    "scripts": {
        "package": "electron-packager ./ 'HelloWorld' --platform=win32 --arch=x64 --icon=icon.ico --out=./out --asar --app-version=0.0.1 --overwrite --electron-version 16.0.1"
    }
}
c、执行打包命令
npm run package

主进程和渲染进程

主进程:启动项目时运行的main.js脚本就是我们说的主进程。在主进程运行的脚本可以以创建 Web 页面的形式展示 GUI。主进程只有一个

渲染进程:每个 Electron 的页面都在运行着自己的进程,这样的进程称之为渲染进程(基于Chromium的多进程结构)。

注意:主进程使用 BrowserWindow 创建实例,主进程销毁后,对应的渲染进程会被终止。主进程与渲染进程通过 IPC 方式(事件驱动)进行通信。

主进程事件生命周期

// 当所有窗口关闭
app.on('window-all-closed', () => {
  console.log('window-all-closed')
  // 对于 MacOS 系统 -> 关闭窗口时,不会直接退出应用
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
// 当应用退出
app.on('quit', () => {
  console.log('quit')
})

app.whenReady().then(() => {
  createWindow()
  // 在MacOS下,当全部窗口关闭,点击 dock 图标,窗口再次打开。
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
})

渲染进程使用Node模块

由于渲染进程运行于浏览器环境中,所以无法使用require来导入Node的模块,例如fs

模块的使用范围

img

1、通过 webPreferences/nodeIntegration(不推荐、不安全)

const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
        //集成node
        nodeIntegration: true,
        //关闭隔离
        contextIsolation: false
    }
})

2、通过 webPreferences/preload 实现,也就是所有关于Node模块的代码写在preload.js

注意:preload.js并不是主进程,仍然是渲染进程,只是可以调用Node模块

const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
        //集成node
        nodeIntegration: true,
        //预加载
        preload: path.resolve(__dirname, './preload.js')
    }
})

这样做的话,主进程和渲染进程隔离了,如果在渲染进程中需要读取preload.js中的属性,就需要使用了electron提供的contextBridge

preload.js

const {contextBridge} = require('electron')

contextBridge.exposeInMainWorld('demoApi',{
    platform: process.platform
})

index.js(声明的api会被绑定到window上)

console.log(window.demoApi)

主进程与渲染进程通信

ipcMain和ipcRenderer

main.js,主进程

const fs = require('fs')
const {ipcMain} = require('electron')

function newFile(content){
    fs.writeFile('D:\\桌面\\electron.txt',content,() => {})
    return 'success'
}

ipcMain.handle('new-file-event',async (e,arg) => {
    return newFile(arg)
})

preload.js,需要将ipcRenderer对象绑定到window对象上,供渲染进程使用

const {contextBridge,ipcRenderer} = require('electron')

contextBridge.exposeInMainWorld('ipcRenderer',ipcRenderer)

index.js,渲染进程

document.getElementById('btn').addEventListener('click',() => {
    window.ipcRenderer.invoke('new-file-event','hahaha').then((msg) => {
        console.log(msg)
    })
})

remote模块

如果需要在渲染进程中,使用主进程的对象,那么需要使用remote模块

如果是electron13以后,那么需要手动加入remote包

npm install --save @electron/remote

主进程共享:

var electron = require('electron')  // 引入electron模块
var app = electron.app   // 创建electron引用
var BrowserWindow = electron.BrowserWindow;  // 创建窗口引用

process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; //关闭安全检查
var mainWindow = null ;  // 声明要打开的主窗口
app.on('ready',()=>{
    mainWindow = new BrowserWindow({})
    
    // 初始化remote
    require('@electron/remote/main').initialize()
    require('@electron/remote/main').enable(mainWindow.webContents)   
    
    mainWindow.loadFile('index.html')  // 加载那个页面
    
    // 分享主进程的数据
    global.shareObject = {
        mainWindow: mainWindow,
        Menu:electron.Menu
    };

})

渲染进程引入:

// 获取主进程全局变量
const shareObject = require('@electron/remote').getGlobal("shareObject")
var Menu = shareObject.Menu;
var mainWindow = shareObject.mainWindow;

主进程

App

路径

如果需要获取 Electron exe 程序的所在目录

process.cwd()

事件

before-quit

在应用程序退出之前触发

app.on('before-quit', (e) => {
  console.log('App is quiting')
  e.preventDefault()
})
browser-window-blur

browserWindow 失去焦点时触发

app.on('browser-window-blur', (e) => {
  console.log('App unfocused')
})
browser-window-focus
app.on('browser-window-focus', (e) => {
  console.log('App focused')
})

方法

app.quit()

退出整个应用

app.quit()
app.getPath(name)

获取系统目录"home" | "appData" | "userData" | "sessionData" | "temp" | "exe" | "module" | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos" | "recent" | "logs" | "crashDumps"

app.getPath('home')

BrowserWindow

创建并控制浏览器窗口,new的时候就会创建显示

创建一个窗口

const win = new BrowserWindow({ options })
win.loadFile('index.html')

//options属性
width 整数型 (可选) - 窗口的宽度(以像素为单位)。 默认值为 800
height 整数型 (可选) - 窗口的高度(以像素为单位)。 默认值为 600
x Interger (可选) - (必选 如果使用了y) 窗口相对于屏幕左侧的偏移量。 默认值为将窗口居中
y Integer (可选) - (必选 如果使用了x) 窗口相对于屏幕顶端的偏移量。 默认值为将窗口居中
useContentSize boolean (可选) - 根据内容大小调整窗口大小。默认值为 false
center boolean (可选) - 窗口是否在屏幕居中. 默认值为 false
minWidth(可选)-窗口的最小宽度。默认为0 默认值为 0
minHeight Integer(可选) - 窗口的最小高度。 默认值为 0
maxWidth Integer(可选)-窗口的最大宽度。 默认值不限
maxHeight Integer (可选) - 窗口的最大高度。 默认值不限
resizable boolean (可选) - 窗口大小是否可调整。 默认值为 true
movable boolean (可选) macOS Windows - 窗口是否可移动。 在 Linux 上未实现。 默认值为 true
minimizable boolean (可选) macOS Windows - 窗口是否可最小化。 在 Linux 上未实现。 默认值为 true
maximizable boolean (可选) macOS Windows - 窗口是否最大化。 在 Linux 上未实现。 默认值为 true
closable boolean (可选) macOS Windows - 窗口是否可关闭。 在 Linux 上未实现。 默认值为 true
alwaysOnTop boolean (可选) - 窗口是否永远在别的窗口的上面。 默认值为 false
fullscreen boolean (可选) - 窗口是否全屏. 当明确设置为 false 时,在 macOS 上全屏的按钮将被隐藏或禁用. 默认值为 false.
kiosk boolean (可选) - 窗口是否进入kiosk模式。 默认值为 false.
titlestring(可选) - 默认窗口标题 默认为"Electron"。 如果由loadURL()加载的HTML文件中含有标签<title>,此属性将被忽略。
icon (NativeImage | string) (可选) - 窗口图标。 在 Windows 上推荐使用 ICO 图标来获得最佳的视觉效果, 默认使用可执行文件的图标.
show boolean (可选) - 窗口是否在创建时显示。 默认值为 true。
frame boolean (可选) - 设置为 false 时可以创建一个无边框窗口 默认值为 true。
parent BrowserWindow (可选) - 指定父窗口 默认值为 null.
backgroundColor string (可选) - 窗口背景色,格式为 Hex, RGB, RGBA, HSL, HSLA 或 CSS 命名颜色。
hasShadow boolean (可选) - 窗口是否有阴影. 默认值为 true。
opacity number (可选) macOS Windows - 设置窗口的初始透明度,在 0.0(全透明)和 1.0(完全不透明)之间 

优雅的显示窗口

// 否则new的时候就会显示窗口
let mainWindow = new BrowserWindow({ show: false })
// 页面加载完毕才会显示
mainWindow.once('ready-to-show', () => {
  mainWindow.show()
})

无边框窗口

mainWindow = new BrowserWindow({
  frame: false
})

让页面可拖拽,这个属性也可以加在其他dom元素上

<body style="user-select: none; -webkit-app-region:drag;">

no-drag 修复下面控件的bug

<input style="-webkit-app-region: no-drag;" type="range" name="range" min="0" max="10">

显示红绿灯

mainWindow = new BrowserWindow({
  titleBarStyle: 'hidden' // or hiddenInset 距离红绿灯更近
})

方法

loadURL和loadHtml

设置当前窗口加载的资源

show和hide

显示或隐藏窗口

getAllWindows
//静态方法,获取所有窗口
let allWindows = BrowserWindow.getAllWindows()
maximize和minimize

窗口最大化和最小化

保持窗口状态

保持窗口最后的状态(位置、大小)

npm install electron-win-state --save
const WinState = require('electron-win-state').default

const winState = new WinState({
    defaultWidth: 1200,
    defaultHeight: 800
})

function creatWindow(){
    let win = new BrowserWindow({
        ...winState.winOptions,
        // 注意:使用winState管理就不要指定大小了
        // width: 1200,
        // height: 800,
    })
    // winState管理当前窗口
    winState.manage(win)
    win.loadFile("index.html")
}

webContents

webContents 是 EventEmitter 的实例,负责渲染和控制网页,是 BrowserWindow 对象的一个属性。

mainWindow.webContents

getAllWebContents

返回 WebContents[] - 所有 WebContents 实例的数组。 包含所有Windows,webviews,opened devtools 和 devtools 扩展背景页的 web 内容

const {app, BrowserWindow, webContents} = require('electron')
console.log(webContents.getAllWebContents())
事件
did-finish-load

资源全部加载完成调用

let wc = mainWindow.webContents
wc.on('did-finish-load', () => {
  console.log('Conent fully loaded')
})
dom-ready

Dom完全加载完成调用

wc.on('dom-ready', () => {
  console.log('DOM Ready')
})
context-menu

监听右键点击事件

wc.on('context-menu', (e, params) => {
  console.log(`Context menu opened on: ${params.mediaType} at x:${params.x}, y:${params.y}`)
})
executeJavaScript

在当前窗口执行js

wc.on('context-menu', (e, params) => {
  wc.executeJavaScript(`alert('${params.selectionText}')`)
})

dialog

显示用于打开和保存文件、警报等的本机系统对话框

const {dialog} = require('electron')

showOpenDialog

打开文件的dialog

dialog.showOpenDialog({
    buttonLabel: '选择',
    defaultPath: app.getPath('desktop'),
    properties: ['multiSelections', 'createDirectory', 'openFile', 'openDirectory']
}).then((result)=> {
    console.log(result)
})

showSaveDialog

保存文件的dialog

dialog.showSaveDialog({}).then(result => {
    console.log(result.filePath)
})

showMessageBox

显示消息对话窗

//Yes、No显示的位置会在下面
const answers = [ 'an1','an2', 'an3', 'Yes', 'No',]
dialog.showMessageBox({
    title: 'Message Box',
    message: 'Please select an option',
    detail: 'Message details.',
    buttons: answers
}).then(({response}) => {
    console.log(`User selected: ${answers[response]}`)
})

快捷键

快捷键:定义键盘快捷键。 系统快捷键:在应用程序没有键盘焦点时,监听键盘事件。

快捷键可以包含多个功能键和一个键码的字符串,由符号+结合,用来定义你应用中的键盘快捷键

快捷方式使用 register 方法在 globalShortcut 模块中注册。

globalShortcut 模块可以在操作系统中注册/注销全局快捷键, 以便可以为操作定制各种快捷键。

注意: 快捷方式是全局的; 即使应用程序没有键盘焦点, 它也仍然在持续监听键盘事件。 在应用程序模块发出 ready 事件之前, 不应使用此模块。

globalShortcut.register('CommandOrControl+M', () => {
        win.maximize()
})
#特殊功能键
Command (缩写为Cmd)
Control (缩写为Ctrl)
CommandOrControl (缩写为 CmdOrCtrl)
Alt
Option
AltGr
Shift
Super
Space
Tab
大写锁定(Capslock)
数字锁定(Numlock)

Menu

设置系统菜单

const {Menu} = require('electron')

const menu = Menu.buildFromTemplate([
    {
      label: 'Electron',
      submenu: [
        { label: 'Item 1'},
        { label: 'Item 2', submenu: [ { label: 'Sub Item 1'} ]},
        { label: 'Item 3'},
      ]
    },
    {
      label: 'Edit',
      submenu: [
        { role: 'undo'},
        { role: 'redo'},
        { role: 'copy'},
        { role: 'paste'},
      ]
    },
    {
      label: 'Actions',
      submenu: [
        {
          label: 'DevTools',
          role: 'toggleDevTools'
        },
        {
          role: 'toggleFullScreen'
        },
        {
          label: 'Greet',
          click: () => { console.log('Hello from Main Menu') },
          accelerator: 'Shift+Alt+G'
        }
      ]
    }
])

Menu.setApplicationMenu(menu)

托盘

const {Tray} = require('electron')
// 定义托盘右键菜单
let trayMenu = Menu.buildFromTemplate([
    { label: 'Item 1' },
    { 
        label: '退出',
        role: 'quit' 
    }
])
// 声明全局变量,避免被垃圾回收掉
let tray = null;

function createTray() {
    // 托盘图标
    tray = new Tray('test.jpg')
    // 鼠标悬浮托盘描述
    tray.setToolTip('Tray details')
    // 托盘的点击事件
    tray.on('click', e => {
        //当按住shift点击
        if (e.shiftKey) {
            app.quit()
        } else {
            //窗口显示或隐藏
            win.isVisible() ? win.hide() : win.show()
        }
	})
	tray.setContextMenu(trayMenu)
}

function creatWindow(){
    // 创建窗口时创建托盘
    createTray()
    ...
}

渲染进程

剪贴板

在系统剪贴板上进行复制和粘贴操作。

在主进程(main process)和渲染进程(renderer process)上均可用。

const {clipboard} = require('electron')

// 返回字符串 - 剪贴板中的内容为纯文本
clipboard.readText()

// 将文本写入系统剪贴板
clipboard.writeText(text)

desktopCapturer

访问可用于从桌面捕获音频和视频的媒体源信息。

注意:目前只能在主进程中调用

const {desktopCapturer} = require('electron')

desktopCapturer.getSources({ 
    types: ['window', 'screen'],
    thumbnailSize: {
        width: 1728,
        height: 1117
    }
}).then(async sources => {
    //sources是一个数组
    console.log(sources)
    return sources
    for (const source of sources) {
        // sources是一个数组
        console.log(sources)
        // 获取一个NativeImage对象
        let str = sources[0].thumbnail.crop({ x: 0, y: 30, width: 1200, height: 1170 })
        return str.toDataURL()
    }
})

NativeImage

使用 PNG 或 JPG 文件创建托盘、dock和应用程序图标。

在 Electron 内, 那些需要图片的 API 可以传递两种参数, 一种是文件路径, 一种是 NativeImage 实例对象。 空的图片对象将被 null 参数替代

// 从剪贴板获取图片
clipboard.readImage()