Electron桌面应用开发

https://www.electronjs.org/

有的时候 样式更新不过来 ctrl + r

调试面板 ctrl + shift + i

一、Electron技术架构

Electron桌面应用开发_第1张图片
Electron集成了chromium与node.js,将他们整合到一个运行环境中,允许我们使用web技术 html、css、js来构建桌面应用程序,并且能过通过一些与操作系统无关的api 访问windows\macos

  • Chromium:可以理解为浏览器,支持最新特性的浏览器,es6
  • Node.js:javascript 运行时,可实现文件读写等
  • Native APIs: 提供统一的原生界面能力

二、Electron工作流程

Electron桌面应用开发_第2张图片

在这里插入图片描述

主进程

  • 可以看做是 package.json 中 main 属性对应的文件
  • 一个应用只会有一个主进程
  • 只有主进程可以进行 GUI 的 API 操作

渲染进程

  • Windows 中展示的界面通过渲染进程表现
  • 一个应用可以有多个渲染进程

三、Electron工作流程

main.js主线程

const { app, BrowserWindow } = require('electron')

// 当 app 启动之后执行窗口创建等操作
app.whenReady().then(() => {
  const mainWin = new BrowserWindow({
    width: 600,
    height: 400
  })

  // 在当前窗口中加载指定界面让它显示具体的内容
  mainWin.loadFile('index.html')

  mainWin.on('close', () => {
    console.log('close~~~~~~')
  })
})

app.on('window-all-closed', () => {
  console.log('all windows is closed')
  app.quit()
})

四、Electron生命周期

  • ready: app 初始化完成
  • dom-ready: 一个窗口中的文本加载完成
  • did-finsh-load: 导航完成时触发
  • window-all-closed: 所有窗口都被关闭时触发
  • before-quit: 在关闭窗口之前触发
  • will-quit: 在窗口关闭并且应用退出时触发
  • quit: 当所有窗口被关闭时触发
  • closed: 当窗口关闭时触发,此时应删除窗口引用

main.js

const { app, BrowserWindow } = require('electron')

// 创建窗口
function createWindow() {
  let mainWin = new BrowserWindow({
    width: 800,
    height: 400
  })

  mainWin.loadFile('index.html')

  mainWin.webContents.on('did-finish-load', () => {
    console.log('33333--->did-finish-load')
  })

  mainWin.webContents.on('dom-ready', () => {
    console.log('22222--->dom-ready')
  })

  mainWin.on('close', () => {
    console.log('88888--->this window is closed')
    mainWin = null
  })
}

app.on('ready', () => {
  console.log('11111----->ready')
  createWindow()
})

app.on('window-all-closed', () => {
  console.log('44444---->window-all-closed')
  app.quit()
})

app.on('before-quit', () => {
  console.log('5555->before-quit')
})

app.on('will-quit', () => {
  console.log('66666->will-quit')
})


app.on('quit', () => {
  console.log('777777-quitquit')
})

//22222--->dom-ready
//33333--->did-finish-load
//88888--->this window is closed
//44444---->window-all-closed
//5555->before-quit
//66666->will-quit
//777777-quitquit

五、窗口尺寸

每次修改代码都要重新执行 npm start比较麻烦,我们可以在package.json中做如下配置:

  "scripts": {
    "start": "nodemon --watch main.js --exec npm run build",
    "build": "electron ."
  },

屏幕出现短暂空白问题,解决:
Electron桌面应用开发_第3张图片

屏幕宽高边界设置
Electron桌面应用开发_第4张图片
禁止屏幕缩放属性
Electron桌面应用开发_第5张图片

六、窗口标题及环境

去掉默认标题 菜单等
在这里插入图片描述Electron桌面应用开发_第6张图片

透明窗体
在这里插入图片描述
保留标题,隐藏菜单

在这里插入图片描述
有的时候 样式更新不过来 ctrl + r

调试面板 ctrl + shift + i

渲染进程里面想使用require,要进行相应的配置,不然报错

  let mainWin = new BrowserWindow({
    icon: 'lg.ico',  // 设置一个图片路径,可以自定义当前应用的显示图标
    title: "标题",  // 自定义当前应用的显示标题
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true
    }
  })

七、自定义窗口

实现窗口最大化、最小化功能
在这里插入图片描述

const { remote } = require('electron')

window.addEventListener('DOMContentLoaded', () => {

  // 利用 remote 可以获取当前窗口对象
  let mainWin = remote.getCurrentWindow()

  // 获取元素添加点击操作的监听
  let aBtn = document.getElementsByClassName('windowTool')[0].getElementsByTagName('div')

  aBtn[0].addEventListener('click', () => {
    // 当前事件发生后说明需要关闭窗口
    mainWin.close()
  })

  aBtn[1].addEventListener('click', () => {
    // 这里需要执行的最大化操作
    console.log(mainWin.isMaximized())
    if (!mainWin.isMaximized()) {
      mainWin.maximize()  // 让当前窗口最大化
    } else {
      mainWin.restore()  // 回到原始的状态
    }
  })

  aBtn[2].addEventListener('click', () => {
    // 实现最小化
    if (!mainWin.isMinimized()) {
      mainWin.minimize()
    }
  })
})

八、阻止窗口关闭

Electron桌面应用开发_第7张图片

window.onbeforeunload


  window.onbeforeunload = function () {
    let oBox = document.getElementsByClassName('isClose')[0]
    oBox.style.display = 'block'

    let yesBtn = oBox.getElementsByTagName('span')[0]
    let noBtn = oBox.getElementsByTagName('span')[1]

    yesBtn.addEventListener('click', () => {
      mainWin.destroy()
    })

    noBtn.addEventListener('click', () => {
      oBox.style.display = 'none'
    })

    return false
  }

九、父子及模态窗口

modal: true 是设置模态窗口 =》父窗口不可以拖动、关闭

const { remote } = require('electron')

window.addEventListener('DOMContentLoaded', () => {
  let oBtn = document.getElementById('btn')
  oBtn.addEventListener('click', () => {
    let subWin = new remote.BrowserWindow({
      parent: remote.getCurrentWindow(),
      width: 200,
      height: 200,
      modal: true
    })
    subWin.loadFile('sub.html')
    subWin.on('close', () => {
      subWin = null
    })
  })
})

十、自定义菜单

Electron桌面应用开发_第8张图片
type: ‘separator’ 设置菜单之间的分隔线
Electron桌面应用开发_第9张图片

const {  Menu } = require('electron')
 // 定义自己需要的菜单项
  let menuTemp = [
    {
      label: '文件',
      submenu: [
        {
          label: '打开文件',
          click() {
            console.log('当前需要做的就是打开某一个具体的文件')
          }
        },
        {
          type: 'separator'
        },
        {
          label: '关闭文件夹'
        },
        {
          label: '关于',
          role: 'about'
        }
      ]
    },
    { label: '编辑' }
  ]

  // 利用上述的模板然后生成一个菜单项
  let menu = Menu.buildFromTemplate(menuTemp)

  // 将上述的自定义菜单添加到应用里
  Menu.setApplicationMenu(menu)

十一、菜单角色及类型

  // 01 自定义菜单的内容
  let menuTemp = [
    {
      label: '角色',
      submenu: [
        { label: '复制', role: 'copy' },
        { label: '剪切', role: 'cut' },
        { label: '粘贴', role: 'paste' },
        { label: '最小化', role: 'minimize' },
      ]
    }
   ] 

Electron桌面应用开发_第10张图片

 {
      label: '类型',
      submenu: [
        { label: '选项1', type: 'checkbox' },
        { label: '选项2', type: 'checkbox' },
        { label: '选项3', type: 'checkbox' },
        { type: "separator" },
        { label: 'item1', type: "radio" },
        { label: 'item2', type: "radio" },
        { type: "separator" },
        { label: 'windows', type: 'submenu', role: 'windowMenu' }
      ]
    },

Electron桌面应用开发_第11张图片

{
      label: '其它',
      submenu: [
        {
          label: '打开',
          icon: './open.png',
          accelerator: 'ctrl + o',
          click() {
            console.log('open操作执行了')
          }
        }
      ]
    }

十二、动态创建菜单

const { remote } = require('electron')
const Menu = remote.Menu
const MenuItem = remote.MenuItem

window.addEventListener('DOMContentLoaded', () => {
  // 获取要应的元素
  let addMenu = document.getElementById('addMenu')
  let menuCon = document.getElementById('menuCon')
  let addItem = document.getElementById('addItem')

  // 自定义全局变量存放菜单项
  let menuItem = new Menu()

  // 生成自定义的菜单
  addMenu.addEventListener('click', () => {
    console.log('111')
    // 创建菜单 
    let menuFile = new MenuItem({ label: '文件', type: 'normal' })
    let menuEdit = new MenuItem({ label: '编辑', type: 'normal' })
    let customMenu = new MenuItem({ label: '自定义菜单项', submenu: menuItem })

    // 将创建好的自定义菜单添加至 menu 
    let menu = new Menu()
    menu.append(menuFile)
    menu.append(menuEdit)
    menu.append(customMenu)

    // 将menu 放置于 app 中显示
    Menu.setApplicationMenu(menu)
  })

  // 动态添加菜单项
  addItem.addEventListener('click', () => {
    // 获取当前 input 输入框当中的内容
    let con = menuCon.value.trim()
    if (con) {
      menuItem.append(new MenuItem({ label: con, type: 'normal' }))
      menuCon.value = ''
    }
  })
})

十三、自定义右键菜单

Electron桌面应用开发_第12张图片

const { remote } = require('electron')
console.log(remote)
const Menu = remote.Menu

// 定义菜单的内容
let contextTemp = [
  { label: 'Run Code' },
  { label: '转到定义' },
  { type: 'separator' },
  {
    label: '其它功能',
    click() {
      console.log('其它功能选项被点击了')
    }
  },
]

// 依据上述的内容来创建 menu 
let menu = Menu.buildFromTemplate(contextTemp)

// 给鼠标右击添加监听
window.addEventListener('DOMContentLoaded', () => {
  window.addEventListener('contextmenu', (ev) => {
    ev.preventDefault()
    menu.popup({ window: remote.getCurrentWindow() })
  }, false)
})

十四、主进程与渲染进程通信

渲染进程 index.js

const { ipcRenderer } = require('electron')

window.onload = function () {
  // 获取元素
  let aBtn = document.getElementsByTagName('button')

  // 01 采用异步的 API 在渲染进程中给主进程发送消息
  aBtn[0].addEventListener('click', () => {
    ipcRenderer.send('msg1', '当前是来自于渲染进程的一条异步消息')
  })

  // 02 采用同步的方式完成数据通信
  aBtn[1].addEventListener('click', () => {
    let val = ipcRenderer.sendSync('msg2', '同步消息')
    console.log(val)
  })


  // 当前区域是接收消息
  ipcRenderer.on('msg1Re', (ev, data) => {
    console.log(data)
  })

  ipcRenderer.on('mtp', (ev, data) => {
    console.log(data)
  })
}

主进程 main.js

  let temp = [
    {
      label: 'send',
      click() {
        BrowserWindow.getFocusedWindow().webContents.send('mtp', '来自于自进程的消息')
      }
    }
  ]

  let menu = Menu.buildFromTemplate(temp)
  Menu.setApplicationMenu(menu)

  mainWin.loadFile('index.html')
  mainWin.webContents.openDevTools()

// 主进程接收消息操作
ipcMain.on('msg1', (ev, data) => {
  console.log(data)
  ev.sender.send('msg1Re', '这是一条来自于主进程的异步消息')
})

ipcMain.on('msg2', (ev, data) => {
  console.log(data)
  ev.returnValue = '来自于主进程的同步消息'
})

十五、基于本地存储的渲染进程间通信1

Electron桌面应用开发_第13张图片
index1.js

const { ipcRenderer } = require('electron')

window.onload = function () {
  // 获取元素
  let oBtn = document.getElementById('btn')

  oBtn.addEventListener('click', () => {
    ipcRenderer.send('openWin2')

    // 打开窗口2之后,保存数据至....
    localStorage.setItem('name', '存储内容')
  })
}

index2.js

window.onload = function () {
  let oInput = document.getElementById('txt')
  let val = localStorage.getItem('name')

  oInput.value = val
}

main.js

parent: BrowserWindow.fromId(mainWinId), 父子窗口设置

const { app, BrowserWindow, ipcMain } = require('electron')

// 定义全局变量存放主窗口 Id
let mainWinId = null

const createWindow = function () {
  let mainWin = new BrowserWindow({
    frame: true,
    show: false,
    title: '标题',
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true
    }
  })

  mainWin.loadFile('index.html')

  mainWinId = mainWin.id

// 接收其它进程发送的数据,然后完成后续的逻辑
ipcMain.on('openWin2', () => {
  // 接收到渲染进程中按钮点击信息之后完成窗口2 的打开
  let subWin1 = new BrowserWindow({
    width: 400,
    height: 300,
    parent: BrowserWindow.fromId(mainWinId),
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true
    }
  })
  subWin1.loadFile('subWin1.html')

  subWin1.on('close', () => {
    subWin1 = null
  })
})

十六、基于本地存储的渲染进程间通讯2

Electron桌面应用开发_第14张图片

父子窗口传值,都是通过主线程做为中转站。

sub.js 子渲染进程

const { ipcRenderer } = require('electron')
window.onload = function () {
  let oInput = document.getElementById('txt')
  let val = localStorage.getItem('name')

  oInput.value = val
  // 在 sub 中发送数据给 index.js 
  let oBtn = document.getElementById('btn')

  oBtn.addEventListener('click', () => {
    ipcRenderer.send('stm', '来自于sub进程')
  })

  // 接收数据
  ipcRenderer.on('its', (ev, data) => {
    console.log(data)
  })
}

中转站 main.js

const { app, BrowserWindow, ipcMain } = require('electron')

// 定义全局变量存放主窗口 Id
let mainWinId = null

const createWindow = function () {
  let mainWin = new BrowserWindow({
    frame: true,
    show: false,
    title: '标题',
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true
    }
  })

  mainWin.loadFile('index.html')

  mainWinId = mainWin.id

// 接收其它进程发送的数据,然后完成后续的逻辑
ipcMain.on('openWin2', (ev, data) => {
  // 接收到渲染进程中按钮点击信息之后完成窗口2 的打开
  let subWin1 = new BrowserWindow({
    width: 400,
    height: 300,
    parent: BrowserWindow.fromId(mainWinId),
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true
    }
  })
  subWin1.loadFile('subWin1.html')

  subWin1.on('close', () => {
    subWin1 = null
  })

  // 此时我们是可以直接拿到 sub 进程的窗口对象,因此我们需要考虑的就是等到它里面的所有内容
  // 加载完成之后再执行数据发送
  subWin1.webContents.on('did-finish-load', () => {
    subWin1.webContents.send('its', data)
  })
})

ipcMain.on('stm', (ev, data) => {
  // 当前我们需要将 data 经过 main 进程转交给指定的渲染进程
  // 此时我们可以依据指定的窗口 ID 来获取对应的渲染进程,然后执行消息的发送
  let mainWin = BrowserWindow.fromId(mainWinId)
  mainWin.webContents.send('mti', data)
})

index.js 父渲染进程

const { ipcRenderer } = require('electron')

window.onload = function () {
  // 获取元素
  let oBtn = document.getElementById('btn')

  oBtn.addEventListener('click', () => {
    ipcRenderer.send('openWin2', '来自于 index 进程')

  })

  // 接收消息
  ipcRenderer.on('mti', (ev, data) => {
    console.log(data)
  })
}

十七、dialog模块

Electron桌面应用开发_第15张图片

const { remote } = require('electron')

window.onload = function () {
  let oBtn = document.getElementById('btn')
  let oBtnErr = document.getElementById('btnErr')

  oBtn.addEventListener('click', () => {
    remote.dialog.showOpenDialog({
      defaultPath: __dirname,
      buttonLabel: '请选择',
      title: '标题',
      properties: ['openFile', 'multiSelections'],
      filters: [
        { "name": '代码文件', extensions: ['js', 'json', 'html'] },
        { "name": '图片文件', extensions: ['ico', 'jpeg', 'png'] },
        { "name": '媒体类型', extensions: ['avi', 'mp4', 'mp3'] }
      ]
    }).then((ret) => {
      console.log(ret)
    })
  })


  oBtnErr.addEventListener('click', () => {
    remote.dialog.showErrorBox('自定义标题', '当前错误内容')
  })
}

十八、shell与iframe

在用户的默认浏览器中打开 URL 的示例:

const { shell } = require('electron')

shell.openExternal('https://github.com')
window.onload = function () {
  // 1 获取元素 
  let oBtn1 = document.getElementById('openUrl')
  let oBtn2 = document.getElementById('openFolder')

  oBtn1.addEventListener('click', (ev) => {
    ev.preventDefault()

    let urlPath = oBtn1.getAttribute('href')

    shell.openExternal(urlPath)
  })

  oBtn2.addEventListener('click', (ev) => {
    shell.showItemInFolder(path.resolve(__filename)) // 打开一个目录
  })
}

在菜单里面打开一个页面


  let tmp = [
    {
      label: '菜单',
      submenu: [
        {
          label: '关于',
          click() {
            shell.openExternal('https://kaiwu.lagou.com/')
          }
        },
        {
          label: '打开',
          click() {
            BrowserWindow.getFocusedWindow().webContents.send('openUrl')
          }
        },
      ]
    }
  ]

  let menu = Menu.buildFromTemplate(tmp)
  Menu.setApplicationMenu(menu)
ipcRenderer.on('openUrl', () => {
  let iframe = document.getElementById('webview')
  iframe.src = 'https://www.lagou.com/'
})

十九、消息通知

Electron桌面应用开发_第16张图片

window.onload = function () {
  let oBtn = document.getElementById('btn')

  oBtn.addEventListener('click', () => {
    let option = {
      title: '标题',
      body: '描述',
      icon: './msg.png'
    }

    let myNotification = new window.Notification(option.title, option)

    myNotification.onclick = function () {
      console.log('点击了消息页卡')
    }
  })
}

二十、快捷键注册

app.on('ready', () => {
  // 注册
  let ret = globalShortcut.register('ctrl + q', () => {
    console.log('快捷键注册成功')
  })

  if (!ret) {
    console.log('注册失败')
  }

  console.log(globalShortcut.isRegistered('ctrl + q'))

  console.log(ret, '~~~~~')

})

app.on('will-quit', () => {
  console.log(666)
  globalShortcut.unregister('ctrl + q')  // 取消注册
  globalShortcut.unregisterAll()  // 全部取消
})

二十一、剪切板模块

const { clipboard, nativeImage } = require('electron')

window.onload = function () {
  // 获取元素
  let aBtn = document.getElementsByTagName('button')
  let aInput = document.getElementsByTagName('input')
  let oBtn = document.getElementById('clipImg')
  let ret = null

  aBtn[0].onclick = function () {
    // 复制内容
    ret = clipboard.writeText(aInput[0].value)
  }

  aBtn[1].onclick = function () {
    // 粘贴内容
    aInput[1].value = clipboard.readText(ret)
  }

  oBtn.onclick = function () {
    // 将图片放置于剪切板当中的时候要求图片类型属于 nativeImage 实例
    let oImage = nativeImage.createFromPath('./msg.png')
    clipboard.writeImage(oImage)

    // 将剪切板中的图片做为 DOM 元素显示在界面上
    let oImg = clipboard.readImage()
    let oImgDom = new Image()
    oImgDom.src = oImg.toDataURL()
    document.body.appendChild(oImgDom)
  }
}

你可能感兴趣的:(electron,javascript,ecmascript)