electron 入门

62 阅读7分钟

electron-vue 入门

基于 vue (基本上是它听起来的样子) 来构造 electron 应用程序的样板代码。

Electron简介

Electron是干什么的? 简单来讲,Electron 使用 JavaScriptHTMLCSS,来构建跨平台的桌面应用程序。

按照官方的说法:如果你可以建一个网站,你就可以建一个桌面应用程序。 和传统的桌面应用相比,使用Electron开发更容易上手,开发效率更高。并且,web技术应用广泛、生态繁荣,Electron可以使用几乎所有的Web生态领域及Node.js生态领域的组件和技术方案。

与网页应用相比,Electron于ChromiumNode.js,可以避免令人头痛的浏览器兼容问题。而Web前端受限访问的文件系统、系统托盘、系统通知等,开发Electron应用时可以自由地使用。

简单理解Electron的工作机制

使用Electron开发的桌面应用,类似于简易版的、定制版的Chrome浏览器,当然这个浏览器中的页面不能通过输入网址打开,而是由开发者写好的。

和浏览器架构类似,Electron应用程序区分主进程渲染进程

  • 主进程: 负责控制应用程序的生命周期、创建和管理应用程序窗口,有着多种控制原生桌面功能的模块,例如:菜单、对话框以及托盘图标等。
  • 渲染进程:负责完成渲染界面、接收用户输入、响应用户的交互等工作。

一个electon应用只有一个主进程,但可以有多个渲染进程

项目搭建 & 打包

项目搭建比较简单,直接使用 electron-vue 的官方模板就可以生成项目,需要安装 vue-cli 命令行工具

npm install -g vue-cli // 需要安装 vue-cli 脚手架
vue init simulatedgreg/electron-vue project-name // 使用 electron-vue 官方模板生成项目
npm install // 安装依赖
npm run dev // 启动项目

转存失败,建议直接上传图片文件

项目打包也比较简单,普通打包执行 npm run build 即可,如果要打包成免安装文件,执行 npm run build:dir,非常方便!

项目打包可能会遇到很多坑,后面有机会再讲解

npm run build // 打包成可执行文件
npm run build:dir // 打包成免安装文件

项目结构

简单的基础调试

  • 窗口页面的调试方法和chrome浏览器类似。点击菜单栏的View --- Toggle Developer Tools,或者按它对应的快捷键,就会出现我们熟悉的开发者工具界面。

无边框窗口

要创建无边框窗口,只需在 BrowserWindow 的 options 中将 frame 设置为 false:

mainWindow = new BrowserWindow({
   frame: false,
   ...
})

标题栏和菜单栏消失了,但也会有几个问题:

  1. 默认情况下,无边框窗口是不可拖拽的。应用程序需要在 CSS 中指定 -webkit-app-region: drag 来告诉 Electron 哪些区域是可拖拽的。
html,body {
  height: 100%;
  width: 100%;
}

body{
  -webkit-app-region: drag;
}

如果用上面的属性使整个窗口都可拖拽,则必须将其中的按钮标记为不可拖拽,否则按钮将无法点击.

.enable-click {
  -webkit-app-region: no-drag;
}
  1. 自定义最大化,最小化,关闭按钮

当我们在运行客户端时,electron称其为渲染进程,electron客户端启动则称为主进程。

最小化、最大化和关闭方法我们需要在主进程调用,而当我们实际触发最小化等等事件时是在渲染进程中的。因此我们需要将渲染进程,也就是实际运行的页面中最小化等等相关事件触发传递给主进程,然后在主进程中做事件监听,当收到渲染进程传来的方法时,调用最小化、最大化、关闭等方法来达到与原生导航栏一直的效果。

IPC通信

这就需要用到IPC通信了。

IPC(Inter-Process Communication),就是进程间通信。Electron应用程序区分主进程和渲染进程,有时候,两者之间需要通信,传输一些数据、发送一些消息。

渲染进程 TO 主进程

比如,点击关闭按钮,就需要渲染进程向主进程发送隐藏主窗口的请求。

渲染进程使用Electron内置的ipcRenderer模块向主进程发送消息,ipcRenderer.send方法的第一个参数是消息管道名称。

<div class="fd-header-right">
    <i class="fa fa-icon-min" @click="setWin('min')"></i>
    <i class="fa" :class="isMax ? 'fa-icon-default' : 'fa-icon-max'"
        @click="setWin('max')"></i>
    <i class="el-icon-close" @click="setWin('close')"></i>
</div>
<script>
const {ipcRenderer} = require("electron");

export default {
    name: 'ccHeader',
    data() {
        return {
            // 是否最大化
            isMax: false
        }
    },
    methods: {
        setWin(type){
            ipcRenderer.send(type);
        }
    }
}
</script>

主进程通过ipcMain接收消息,ipcMain.on方法的第一个参数也为消息管道的名称,与ipcRenderer.send的名称对应,第二个参数是接收到消息的回调函数。

import { app, BrowserWindow, Menu, ipcMain } from 'electron'
//入口文件index.js
ipcMain.on('min', () => mainWindow.minimize());
ipcMain.on('max', () => {
    if (mainWindow.isMaximized()) {
        mainWindow.restore();
    } else {
        mainWindow.maximize()
    }
});
ipcMain.on('close', () => mainWindow.close());

主进程 TO 渲染进程

主进程向渲染进程发送消息是通过渲染进程的webContents。在mainWindow渲染进程设定了任务后,会传输给主进程任务信息,当任务时间到了,主进程会创建提醒窗口remindWindow,并通过remindWindow.webContents将任务名称发给remindWindow。

function createRemindWindow (task) {
 
  remindWindow = new BrowserWindow({
     //options
  })
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
  
  //主进程发送消息给渲染进程
  remindWindow.webContents.send('setTask', task)

}

在remindWindow渲染进程中,通过ipcRenderer.on接受消息。

ipcRenderer.on('setTask', (event,task) => {
   document.querySelector('.reminder').innerHTML = 
      `<span>${decodeURIComponent(task)}</span>的时间到啦!`
})

渲染进程 TO 渲染进程

渲染进程之间传递消息,可以通过主进程中转,即窗口A先把消息发送给主进程,主进程再把这个消息发送给窗口B,这种非常常见。

  1. 当点击自定义的窗口关闭按钮,我们并不希望退出程序,只是将窗口隐藏,可以通过系统托盘再次打开窗口。

系统托盘

程序启动时,将应用程序加入系统托盘。在Electron中,借助Tray(系统托盘)模块实现。

const { app, BrowserWindow, Tray, Menu } = electron
const iconPath = path.join(__dirname, './src/img/icon.png')
let mainWindow, tray
app.on('ready', () => {
  mainWindow = new BrowserWindow({
    //... options
  })
  mainWindow.loadURL(`file://${__dirname}/src/main.html`)
  
  tray = new Tray(iconPath)      //实例化一个tray对象,构造函数的唯一参数是需要在托盘中显示的图标url  
  
  tray.setToolTip('Tasky')       //鼠标移到托盘中应用程序的图标上时,显示的文本
  
  tray.on('click', () => {       //点击图标的响应事件,这里是切换主窗口的显示和隐藏
    if(mainWindow.isVisible()){
      mainWindow.hide()
    }else{
      mainWindow.show()
    }
  })
  
  tray.on('right-click', () => {    //右键点击图标时,出现的菜单,通过Menu.buildFromTemplate定制,这里只包含退出程序的选项。
    const menuConfig = Menu.buildFromTemplate([
      {
        label: '退出',
        click: () => app.quit()
      }
    ])
    tray.popUpContextMenu(menuConfig)
  })

})

窗口位置

当任务时间到,提醒窗口会在屏幕右下角出现。怎样设定窗口位置呢?

function openCalendarWindow() {
    // 先判断是否已经打开过一个了
    if (calendarWin) {
        calendarWin.close();
    }
    calendarWin = new BrowserWindow({
        width: 400,
        height: 550,
        parent: mainWindow, // win是主窗口
        frame: true, // 是否显示顶部工具栏
        webPreferences: {
            nodeIntegration: true
        }
    })
     //获取屏幕尺寸
    const size = screen.getPrimaryDisplay().workAreaSize
    //获取托盘位置的y坐标(windows在右下角,Mac在右上角)
    const { y } = tray.getBounds()
    //获取窗口的宽高
    const { height, width } = calendarWin.getBounds()
    //计算窗口的y坐标
    const yPosition = process.platform === 'darwin' ? y : y - height
    //setBounds设置窗口的位置
    calendarWin.setBounds({
        x: size.width - width,     //x坐标为屏幕宽度 - 窗口宽度
        y: yPosition,
        height,
        width
    })
    //当有多个应用时,提醒窗口始终处于最上层
    calendarWin.setAlwaysOnTop(true)
    // calendarWin.webContents.closeDevTools();
    // 取消菜单栏
    Menu.setApplicationMenu(null);
    calendarWin.loadURL(winURL + '#/task');
    calendarWin.on('closed', () => { calendarWin = null })
}

关闭窗口

提醒窗口会在一段时间后关闭,calendarWin.close()来关闭窗口。

当窗口关闭后,我们可以设置calendarWin = null来回收分配给该渲染进程的资源。

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

常见功能使用

import { app, BrowserWindow, Menu, ipcMain } from 'electron'

// src/main/index.js

function createWindow () {
  /**
   * Initial window options
   */
    mainWindow = new BrowserWindow({
        height: 563,
        useContentSize: true,
        // resizable: false,   // 是否允许用户改变窗口大小
        width: 1000,
        // fullscreen: true, // 全屏显示
        frame: false, // 是否显示顶部工具栏
        // MAC
        titleBarStyle: 'hidden',
      	// webPreferences:{
        //   backgroundThrottling: false,   //设置应用在后台正常运行
        //   nodeIntegration:true,     //设置能在页面使用nodejs的API
        //   contextIsolation: false
        // }
    })

  mainWindow.loadURL(winURL);
  // 打开f12  ctr+shift+i
  
  // 关闭开发者工具
  mainWindow.webContents.closeDevTools();
  
  // 取消菜单栏
  Menu.setApplicationMenu(null);

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

参考资料

electron-vue打包所遇问题

electron

electron-vue