Electron-vue开发实战1——Main进程和Renderer进程的简单开发

前言

前段时间,我用electron-vue开发了一款跨平台(目前支持Mac和Windows)的免费开源的图床上传应用——PicGo,在开发过程当中踩了很多的坑,不只来自应用的业务逻辑自己,也来自electron自己。在开发这个应用过程当中,我学了很多的东西。由于我也是从0开始学习electron,因此不少经历应该也能给初学、想学electron开发的同窗们一些启发和指示。故而写一份Electron的开发实战经历,用最贴近实际工程项目开发的角度来阐述。但愿能帮助到你们。javascript

预计将会从几篇系列文章或方面来展开:css

  1. electron-vue入门
  2. Main进程和Renderer进程的简单开发
  3. 引入基于Lodash的json database——lowdb
  4. 跨平台的一些兼容措施
  5. 经过CI发布以及更新的方式
  6. ...(想到再写)

说明

PicGo是采用electron-vue开发的,因此若是你会vue,那么跟着一块儿来学习将会比较快。若是你的技术栈是其余的诸如reactangular,那么纯按照本教程虽然在render端(能够理解为页面)的构建可能学习到的东西很少,不过在main端(electron的主进程)应该仍是能学习到相应的知识的。html

若是以前的文章没阅读的朋友能够先从以前的文章跟着看。前端

Main进程和Renderer进程的基本认识

从上一篇文章结尾部分咱们运行成功的一个electron-vue的DEMO来直观看看这两个进程的粗浅认识:vue

能够看到Main进程管理的是这个app窗口(BrowserWindow),而Renderer进程负责的就是咱们熟悉的页面UI渲染。不过实际上,它们远远不只如此。下面一张图可以把它们所支持、管理的electron或者原生的模块大体列出来:java

main & renderer process tree

图中列出来的大部分模块都是咱们会在开发过程当中用到的。react

它们有各自的模块,也有共有的模块好比clipboard等。还有一部分是Main进程里的模块,不过能够经过remote模块,让renderer进程也能使用。好比Menu好比shell等。linux

了解一下哪些模块在哪些进程里,哪些模块能够经过remote模块让renderer进程也能使用是有必要的,这样咱们后续开发的时候才能正确的使用。webpack

上面的模块可能有些从名字里并不能看出做用是啥,不要紧,后续的内容会慢慢涉及。nginx

Main进程开发

上面说到了Main进程一个显著的做用就是建立app的窗口。咱们来看看这个是怎么实现的。

import { app, BrowserWindow } from 'electron' // 从electron引入app和BrowserWindow

let mainWindow

const winURL = process.env.NODE_ENV === 'development'
  ? `http://localhost:9080` // 开发模式的话走webpack-dev-server的url
  : `file://${__dirname}/index.html`

function createWindow () { // 建立窗口
  /** * Initial window options */
  mainWindow = new BrowserWindow({
    height: 563,
    useContentSize: true,
    width: 1000
  }) // 建立一个窗口

  mainWindow.loadURL(winURL) // 加载窗口的URL -> 来自renderer进程的页面

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.on('ready', createWindow) // app准备好的时候建立窗口
复制代码

暂且先无论渲染进程里的页面长什么样,在app准备好的时候打开一个窗口只须要调用一个建立BrowserWindow的方法便可。

main进程里的开发有点当年写jQuery的样子,比较多的是事件驱动型的写法。

app

首先须要注意的是app的模块。这个模块是electron应用的骨架。它掌管着整个应用的生命周期钩子,以及不少其余事件钩子。

app的经常使用生命周期钩子以下:

  • will-finish-launching 在应用完成基本启动进程以后触发
  • ready 当electron完成初始化后触发
  • window-all-closed 全部窗口都关闭的时候触发,在windows和linux里,全部窗口都退出的时候一般是应用退出的时候
  • before-quit 退出应用以前的时候触发
  • will-quit 即将退出应用的时候触发
  • quit 应用退出的时候触发

而咱们一般会在ready的时候执行建立应用窗口、建立应用菜单、建立应用快捷键等初始化操做。而在will-quit或者quit的时候执行一些清空操做,好比解绑应用快捷键。

特别的,在非macOS的系统下,一般一个应用的全部窗口都退出的时候,也是这个应用退出之时。因此能够配合window-all-closed这个钩子来实现:

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') { // 当操做系统不是darwin(macOS)的话
    app.quit() // 退出应用
  }
})
复制代码

除了上面说的生命周期钩子以外,还有一些经常使用的事件钩子:

  • active(仅macOS)当应用处于激活状态时
  • browser-window-created 当一个BrowserWindow被建立的时候
  • browser-window-focus 当一个BrowserWindow处于激活状态的时候

这些钩子须要配合一些具体场景来作出具体的操做。好比当一个BrowserWindow处于激活状态的时候修改窗口的title值。

固然,app这个模块除了上述的一些事件钩子以外,还有一些很经常使用的方法:

  • app.quit() 用于退出应用
  • app.getPath(name) 用于获取一些系统目录,对于存放应用的配置文件等颇有用
  • app.focus() 用于激活应用,不一样系统激活逻辑不同

这些事件和方法都是怎么知道的呢?固然是官方文档了。不过并不须要一开始就通读一遍官方的api文档。官方的api文档更多的做用是用来查阅,当你要开发到某个功能的时候再去查它可否有对应的api、怎么使用。

BrowserWindow

BrowserWindow模块用于建立最多见的应用窗口。对于不一样系统,建立的窗口的默认样式也不太同样。下面来看看macOS和windows的窗口在外观上的区别:

mac版的

windows版的

能够看到两者在窗口顶部的操做区(最小化、最大化、关闭)和标题的位置以及菜单的位置仍是有明显的不一样的。它们跟系统原生的窗口是一致的。不过若是你想要美化一下也是没问题的。好比:

mac版的PicGo

picgo-mac

和windows的PicGo

picgo-windows

其中mac版用了系统的操做区,而windows则没有用系统的操做区,而是用图标模拟的。不过一样的地方是都未使用系统默认的titlebar。这个以后会结合renderer进程来讲。

让咱们来看看建立一个BrowserWindow的经常使用配置:

let window

function createWindow () {
  window = new BrowserWindow({
    height: 900, // 高
    width: 400, // 宽
    show: false, // 建立后是否显示
    frame: false, // 是否建立frameless窗口
    fullscreenable: false, // 是否容许全屏
    center: true, // 是否出如今屏幕居中的位置
    backgroundColor: '#fff' // 背景色,用于transparent和frameless窗口
    titleBarStyle: 'xxx' // 标题栏的样式,有hidden、hiddenInset、customButtonsOnHover等
    resizable: false, // 是否容许拉伸大小
    transparent: true, // 是不是透明窗口(仅macOS)
    vibrancy: 'ultra-dark', // 窗口模糊的样式(仅macOS)
    webPreferences: {
      backgroundThrottling: false // 当页面被置于非激活窗口的时候是否中止动画和计时器
    }
    // ... 以及其余可选配置
  })

  window.loadURL(url)

  window.on('closed', () => { window = null })
}
复制代码

窗口的长宽天然没必要说,须要指定。其中须要注意的几个比较重要的就是,frame这个选项,默认是true。若是选择了false则会建立一个frameless窗口,建立一个没有顶部工具栏、没有border的窗口。这个也是咱们在windows系统下自定义顶部栏的基础。

像上述PicGo的主窗口的配置,就是经过以下的配置实现的:

const createSettingWindow = () => {
  const options = {
    height: 450,
    width: 800,
    show: false,
    frame: true,
    center: true,
    fullscreenable: false,
    resizable: false,
    title: 'PicGo',
    vibrancy: 'ultra-dark',
    transparent: true,
    titleBarStyle: 'hidden',
    webPreferences: {
      backgroundThrottling: false
    }
  }
  if (process.platform === 'win32') { // 针对windows平台作出不一样的配置
    options.show = true // 建立即展现
    options.frame = false // 建立一个frameless窗口
    options.backgroundColor = '#3f3c37' // 背景色
  }
  settingWindow = new BrowserWindow(options)

  settingWindow.loadURL(settingWinURL)

  settingWindow.on('closed', () => {
    settingWindow = null
  })
}
复制代码

app模块同样,BrowserWindow也有不少经常使用的事件钩子:

  • closed 当窗口被关闭的时候
  • focus 当窗口被激活的时候
  • show 当窗口展现的时候
  • hide 当窗口被隐藏的时候
  • maxmize 当窗口最大化时
  • minimize 当窗口最小化时
  • ...

固然,也依然有不少实用的方法:

  • BrowserWindow.getFocusedWindow() [静态方法]获取激活的窗口
  • win.close() [实例方法,下同]关闭窗口
  • win.focus() 激活窗口
  • win.show() 显示窗口
  • win.hide() 隐藏窗口
  • win.maximize() 最大化窗口
  • win.minimize() 最小化窗口
  • win.restore() 从最小化窗口恢复
  • ...

针对不一样的业务逻辑你须要对窗口进行不同的操做。这个须要跟你的项目需求相匹配。好比上述说到的,windows的顶部的操做区(放大、缩小、关闭按钮)就能够经过icon模拟+实例方法来实现。

Tray

一开始看这个名字你可能并不知道这个是个什么东西。能够把它理解为不一样系统的任务栏里的图标组件吧。

好比在macOS里,Tray配合上图标以后就是顶部栏里的应用图标了:

好比在windows里,Tray配合上图标以后就是windows右下角的应用图标了:

须要注意的是,windows和macOS里,图标的大小都是16*16px。macOS下顶部栏的图标一般都是走黑白路线,因此能够为两种系统分别准备不一样的图标。PicGoTray的生成代码大体以下:

function createTray () {
  const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
  tray = new Tray(menubarPic) // 指定图片的路径
  // ... 其余代码
}
复制代码

注意上述代码里有一个${__static}的变量。该变量是electron-vue为咱们暴露出来的项目根目录下的static文件夹的路径。经过这个路径,在开发和生产阶段都能很好的定位你的静态资源所在的目录。是个很方便的变量。

固然Tray并不仅是一个图标而无其余做用了。Tray支持不少有用的事件。其中最关键的两个是clickright-click。分别对应鼠标左键点击和鼠标右键点击事件。

鼠标左键点击事件

  • 在macOS系统下,鼠标左键点击Tray的icon可能会出现配置菜单,也有可能会出现应用窗口。
  • 在windows下,鼠标左键点击Tray的icon一般会出现应用的窗口。

鼠标右键点击事件

  • 在macOS系统下,鼠标右键点击Tray的icon一般会出现配置菜单。
  • 在windows系统下,同上。

因此须要咱们去适配不一样操做系统下用户的操做习惯。

对应于PicGo而言,在macOS系统下左键点击会出现一个menubar的小窗口,右键点击会出现配置菜单。而在windows下,左键点击会直接出现主窗口,(由于在windows下无小窗口的必要),右键点击会出现配置菜单。它们在PicGo里的实现以下:

function createTray () {
  const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
  tray = new Tray(menubarPic)
  const contextMenu = // ...菜单
  tray.on('right-click', () => { // 右键点击
    window.hide() // 隐藏小窗口
    tray.popUpContextMenu(contextMenu) // 打开菜单
  })
  tray.on('click', () => { // 左键点击
    if (process.platform === 'darwin') { // 若是是macOS
      toggleWindow() // 打开或关闭小窗口
    } else { // 若是是windows
      window.hide() // 隐藏小窗口
      if (settingWindow === null) { // 若是主窗口不存在就建立一个
        createSettingWindow()
        settingWindow.show()
      } else { // 若是主窗口在,就显示并激活
        settingWindow.show()
        settingWindow.focus()
      }
    }
  })
}
复制代码

对于macOS而言,Tray还有一个很棒的特性——能够拖拽文件到Tray的icon上,会触发以下事件:

  • drop 当任何东西拖拽到icon上时
  • drop-files 当文件被拖拽到icon上时
  • drop-text 当文本被拖拽到icon上时
  • drop-enter 当刚拖拽到icon上时
  • drop-leave 当拖拽事件离开icon时
  • drop-end 当拖拽事件结束时

就像PicGo实现的拖拽图片到Tray的icon上时实现图片上传的功能,就是用到了上述的一些事件:

尤为注意到在拖拽上的时候和拖拽结束后的时候icon是不同的。在PicGo里是这样实现的,很简单:

tray.on('drag-enter', () => {
    tray.setImage(`${__static}/upload.png`)
  })

  tray.on('drag-end', () => {
    tray.setImage(`${__static}/menubar.png`)
  })
复制代码

Tray另外一个重要的做用就是开启菜单项。这个将结合下一节Menu一块儿说明。

Menu

electron威力强大的Menu组件,既可以生成系统菜单项,也能实现绑定应用经常使用快捷键的功能。

先来看看什么是系统菜单项:

macOS

windows

主要分两种。

  • 第一种是app的菜单。对于macOS来讲就是顶部栏左侧区域的菜单项。对于windows而言就是一个窗口的标题栏下方的菜单区。
  • 第二种是相似于右键菜单的菜单。

第一种菜单能够经过Menu.setApplicationMenu()来实现。

第二种菜单能够经过两个步骤来展现:

1. 建立菜单:

const contextMenu = Menu.buildFromTemplate([...])
复制代码

2. 展现菜单:

tray.on('right-click', () => { // 右键点击tray的时候
  tray.popUpContextMenu(contextMenu) // 弹出菜单
})
复制代码

这里咱们只介绍了Menu自己。其实组成Menu的是一个一个的MenuItem。它们有不少类型:

  1. normal
  2. separator
  3. submenu
  4. checkbox
  5. radio

以及不少角色:

  1. quit
  2. copy
  3. redo
  4. undo
  5. minimize
  6. close
  7. reload
  8. ...

一般来讲,配置的菜单项基本从类型里来组合。好比PicGo的菜单项:

这里面就有normal、submenu、checkbox和radio四种类型。其中默认是normal。

角色的话一般对应的是一些常见的行为。好比quit是退出app,好比minimize是最小化,好比copy是复制。不过须要注意的是,若是你没有在建立app菜单里指定这些操做的快捷键的话,那么一些常见的快捷操做就没法在你的app里使用了。好比ctrl+c或者command+c复制这个操做,若是你没有经过Menu.setApplicationMenu()来设定这个快捷键的话,那么在你的electron应用里就没法执行复制的操做了。PicGo在早期版本里也犯了这个错误。当时的问题是我在开发模式下是没有问题的,可是在生产模式下就没法进行复制粘贴操做。后来查了一下缘由,发现原来在开发模式下,electron会置入默认的一些快捷操做菜单,如图:

因此在生产模式若是我没有置入这些快捷键的话,使用者就没法使用了。这个是大坑

说了这么多,来看看生成app的菜单的代码长啥样:

注意,若是在开发模式下直接只使用以下快捷键的话,一些调试快捷键好比F12或者command+shift+i打开控制台的操做就没法使用了。因此在开发模式下不须要建立这些快捷键菜单。

const createMenu = () => {
  if (process.env.NODE_ENV !== 'development') {
    const template = [{
      label: 'Edit',
      submenu: [
        { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
        { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
        { type: 'separator' },
        { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
        { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
        { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
        { label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
        {
          label: 'Quit',
          accelerator: 'CmdOrCtrl+Q',
          click () {
            app.quit()
          }
        }
      ]
    }]
    menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  }
}
复制代码

能够经过accelerator指定你想要的快捷键。诸如ShiftCtrlCmd等键位缩写。若是是组合键,就加上+。尤为注意到,由于macOS和windows键位的差别,因此有一个很好用的键位缩写CmdOrCtrl,即若是是在macOS上就是Cmd,在windows上就是Ctrl

而后再来看看Tray的“右键”菜单的生成:

const contextMenu = Menu.buildFromTemplate([
    {
      label: '关于',
      click () {
        dialog.showMessageBox({
          title: 'PicGo',
          message: 'PicGo',
          detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
        })
      }
    },
    {
      label: '打开详细窗口',
      click () {
        if (settingWindow === null) {
          createSettingWindow()
          settingWindow.show()
        } else {
          settingWindow.show()
          settingWindow.focus()
        }
      }
    },
    {
      label: '选择默认图床',
      type: 'submenu',
      submenu: [
        {
          label: '微博图床',
          type: 'radio',
          checked: db.read().get('picBed.current').value() === 'weibo',
          click () {
            db.read().set('picBed.current', 'weibo')
              .write()
          }
        },
        {
          label: '七牛图床',
          type: 'radio',
          checked: db.read().get('picBed.current').value() === 'qiniu',
          click () {
            db.read().set('picBed.current', 'qiniu')
              .write()
          }
        },
        {
          label: '腾讯云COS',
          type: 'radio',
          checked: db.read().get('picBed.current').value() === 'tcyun',
          click () {
            db.read().set('picBed.current', 'tcyun')
              .write()
          }
        },
        {
          label: '又拍云图床',
          type: 'radio',
          checked: db.read().get('picBed.current').value() === 'upyun',
          click () {
            db.read().set('picBed.current', 'upyun')
              .write()
          }
        }
      ]
    },
    {
      label: '打开更新助手',
      type: 'checkbox',
      checked: db.get('picBed.showUpdateTip').value(),
      click () {
        const value = db.read().get('picBed.showUpdateTip').value()
        db.read().set('picBed.showUpdateTip', !value).write()
      }
    },
    {
      role: 'quit',
      label: '退出'
    }
  ])

  tray.on('right-click', () => {
    tray.popUpContextMenu(contextMenu)
  })
复制代码

注意,菜单项的点击事件能够直接经过click属性来指定。上面咱们是先经过了Menu.buildFromTemplate()这个方法建立了菜单,而后再在右键点击Tray图标的时候将其弹(PopUp)出来。

固然也有其余构建菜单的方法。能够经过Menu实例的append方法来加入Menu Item。以下例:

const menu = new Menu()
menu.append(new MenuItem({ label: 'Cut', accelerator: 'CmdOrCtrl+X' }))
menu.append(new MenuItem({ type: 'separator' })) // 分割线
menu.append(new MenuItem({ label: 'Helper', type: 'checkbox', checked: true }))
复制代码

基本上有了上述的几个基本模块,咱们的一个应用的骨架是基本搭建好了,拥有窗口、任务栏应用图标和菜单项。其余的Main进程的模块,并非必须的,当会用到的时候将在以后的文章里逐步说起。下一节咱们未来看renderer进程的开发。

Renderer进程开发

对于electron-vue而言,renderer进程其实大部分就是在写咱们平时常写的前端页面罢了。不过相对于平时在浏览器里写的页面,在electron里写页面的时候你还能用到很多非浏览器端的模块,好比fs,好比electron经过remote模块暴露给renderer进程的模块。接下去咱们来看看renderer进程有哪些须要注意的地方。

请使用Hash模式

往常咱们在写Vue的时候都比较喜欢开启路由的history模式,由于这样在浏览器的地址栏上看起来比较好看——没有hash的#号,就如同请求后端的url通常。然而须要注意的是,history模式须要后端服务器的支持。

可能不少朋友平时开发的时候没有感受,那是由于vue-cli里在开发模式下启动的webpack-dev-server帮你实现了服务端的history-fallback的特性。因此在实际部署的时候,至少都须要在你的web服务器程序诸如nginxapache等配置相关的规则,让前端路由返回给vue-router去处理。

而electron里也是如此。在开发模式下,因为使用的是webpack-dev-server开启的服务器,因此BrowserWindow加载的是来自于相似``http://localhost:9080这样的地址的页面。而在生产模式下,倒是使用的file://的协议,好比file://${__dirname}/index.html`来指定窗口加载的页面。

所以,从上面的表述你也能明白了。假如我有一个子路由地址为child。若是不启用Hash模式,在开发模式下没啥问题,http://localhost:9080/child,可是在生产模式下,file://${__dirname}/index.html/child倒是没法匹配的一条路径。所以在electron下,vue-router请不要使用history模式,而使用默认的hash模式。

那么上面的问题就迎刃而解,变为file://${__dirname}/index.html#child便可。

PicGo里加载的页面路由规则以下,从中你也能看出我使用的是hash模式。

const winURL = process.env.NODE_ENV === 'development'
  ? `http://localhost:9080`
  : `file://${__dirname}/index.html`
const settingWinURL = process.env.NODE_ENV === 'development'
  ? `http://localhost:9080/#setting/upload`
  : `file://${__dirname}/index.html#setting/upload`
复制代码

实现本身的titlebar

在上面讲BrowserWindow的时候,我说到有时为了应用的美观,并不想让咱们的应用窗口采用系统默认的titlebar,而想用本身写的来实现。这样的话就在建立你的BrowserWindow的配置里加上一句

titleBarStyle: 'hidden'
复制代码

这样就好了。而后你就能够自行在renderer进程的页面里模拟一个顶部的titlebar了,好比上面提到的PicGotitlebar的样子。实际上代码也很简单:

<div class="fake-title-bar">
  PicGo - {{ version }}
  <div class="handle-bar" v-if="os === 'win32'"> <!-- 若是是windows系统 就加上模拟的操做按钮-->
    <i class="el-icon-minus" @click="minimizeWindow"></i>
    <i class="el-icon-close" @click="closeWindow"></i>
  </div>
</div>
复制代码

而后把这个titlebar的position置顶便可。

不过在平时的使用中,咱们要注意,通常咱们鼠标按住titlebar的时候是能够拖动窗口的。可是若是咱们在不加可拖拽的属性以前,咱们本身写的titlebar是不具有这样的特性的。要加上这个特性也很简单:

.fake-title-bar {
  -webkit-app-region drag
}
复制代码

只需一条CSS,便可让你的titlebar能够拖拽。

不过在windows下,操做区的按钮(缩小、放大、关闭)长按应该是不能拖拽的,因此还须要:

.handle-bar {
  -webkit-app-region no-drag
}
复制代码

变成no-drag,这样就实现了咱们本身生成应用的titlebar了。

drag&drop的避免

一般咱们用Chrome的时候,有个特性是好比你往Chrome里拖入一个pdf,它就会自动用内置的pdf阅读器打开。你往Chrome里拖入一张图片,它就会打开这张图片。因为咱们的electron应用的BrowserWindow其实内部也是一个浏览器,因此这样的特性依然存在。而这也是不少人没有注意的地方。也就是当你开发完一个electron应用以后,往里拖入一张图片,一个pdf等等,若是不是一个可拖拽区域(好比PicGo的上传区),那么它就不该该打开这张图、这个pdf,而是将其排除在外。

因此咱们将在全局监听dragdrop事件,当用户拖入一个文件可是又不是拖入可拖拽区域的时候,应该将其屏蔽掉。由于全部的页面都应该要有这样的特性,因此我写了一个vue的mixin

export default {
  mounted () {
    this.disableDragEvent()
  },
  methods: {
    disableDragEvent () {
      window.addEventListener('dragenter', this.disableDrag, false)
      window.addEventListener('dragover', this.disableDrag)
      window.addEventListener('drop', this.disableDrag)
    },
    disableDrag (e) {
      const dropzone = document.getElementById('upload-area') // 这个是可拖拽的上传区
      if (dropzone === null || !dropzone.contains(e.target)) {
        e.preventDefault()
        e.dataTransfer.effectAllowed = 'none'
        e.dataTransfer.dropEffect = 'none'
      }
    }
  },
  beforeDestroy () {
    window.removeEventListener('dragenter', this.disableDrag, false)
    window.removeEventListener('dragover', this.disableDrag)
    window.removeEventListener('drop', this.disableDrag)
  }
}
复制代码

这样在全局引入这个mixin便可。

remote模块的使用

remote模块是electron为了让一些本来在Main进程里运行的模块也能在renderer进程里运行而建立的。如下说几个咱们会用到的。

electron-vue里内置了vue-electron这个模块,能够在vue里很方便的使用诸如this.$electron.remote.xxx来使用remote的模块。

shell

shell模块的官方说明是:Manage files and URLs using their default applications.也就是使用文件或者URL的默认应用。一般咱们能够用其让默认图片应用打开一张图片、让默认浏览器打开一个url。

若是咱们想在renderer进程里点击一个按钮而后在默认浏览器里打开一个url的话就能够这样:

<button @click="openURL"></button>

<script> export default { methods: { openURL () { this.$electron.remote.shell.openExternal('https://github.com/Molunerfinn/PicGo') } } } </script>
复制代码

是否是很方便?

更多详细的shell的用法能够参考文档

dialog

有的时候咱们会有打开原生的对话框的需求。好比PicGo的版本信息:

macOS

windows

这个时候就能够经过dialog这个模块来实现了。逻辑跟上面同样也是点击一个按钮打开一个dialog:

openDialog () {
  this.$electron.remote.dialog.showMessageBox({
    title: 'PicGo',
    message: 'PicGo',
    detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
  })
}
复制代码

更多详细的dialog的用法能够参考文档

Menu和BrowserWindow的应用

使用Menu可能不少人可以理解。可是为何要使用BrowserWindow呢?由于须要定位你打开Menu的窗口。

在PicGo里,有一个点击按钮打开Menu的操做,大体以下:

buildMenu () {
    const template = [...]
    this.menu = Menu.buildFromTemplate(template)
  },
  openDialog () {
    this.menu.popup(remote.getCurrentWindow) // 获取当前打开Menu的窗口
  }
复制代码

这里的menu.popup就须要你指定一下打开这个menu的窗口。它将自动定位你点击的位置而弹出。

main进程和renderer进程的通讯

在Vue里,若是是非父子组件通讯,很经常使用的是经过Bus Event来实现的。而electron里的不一样进程间的通讯其实也很相似,是经过ipcMainipcRenderer来实现的。其中ipcMain是在main进程里使用的,而ipcRenderer是在renderer进程里使用的。

ipcMain和ipcRenderer

官网的例子其实很简洁明了了,我放出来:

// In main process.
const {ipcMain} = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
  console.log(arg)  // prints "ping"
  event.sender.send('asynchronous-reply', 'pong')
})

ipcMain.on('synchronous-message', (event, arg) => {
  console.log(arg)  // prints "ping"
  event.returnValue = 'pong'
})
复制代码
// In renderer process (web page).
const {ipcRenderer} = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"

ipcRenderer.on('asynchronous-reply', (event, arg) => {
  console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
复制代码

其中ipcMain只有监听来自ipcRenderer的某个事件后才能返回给ipcRenderer值。而ipcRenderer既能够收,也能够发。

那么问题就来了,如何让ipcMain主动发送消息呢?或者说让main进程主动发送消息给ipcRenderer

首先要明确的是,ipcMain没法主动发消息给ipcRenderer。由于ipcMain只有.on()方法没有.send()的方法。因此只能用其余方法来实现。有办法么?有的,用webContents

webContents

webContents实际上是BrowserWindow实例的一个属性。也就是若是咱们须要在main进程里给某个窗口某个页面发送消息,则必须经过win.webContents.send()方法来发送。

代码大体以下:

// In main process
let win = new BrowserWindow({...})
win.webContents.send('img-files', imgs)
复制代码
// In renderer process
ipcRenderer.on('img-files', (event, files) => {
  console.log(files)
})
复制代码

因此必须指定要发送的窗口,才能将信息准确送达。

总结

本文详细地讲述了electron里Main进程和Renderer进程的基础知识和开发相关。不少都是我在开发PicGo的时候碰到的问题、踩的坑。也许文中简单的几句话背后就是我无数次的查阅和调试。内容相比第一篇多了很多,但愿这篇文章可以给你的electron-vue开发带来一些启发。文中相关的代码,你均可以在PicGo的项目仓库里找到,欢迎点个star~但愿本文可以给你带来帮助,这是我最开心的地方。若是喜欢,欢迎关注个人博客以及本系列文章的后续进展。

注:文中的图片除未特意说明以外均属于我我的做品,须要转载请私信

相关文章
相关标签/搜索