Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Electron 实战桌面计算器应用 #22

Open
lin-xin opened this issue Sep 3, 2017 · 2 comments
Open

Electron 实战桌面计算器应用 #22

lin-xin opened this issue Sep 3, 2017 · 2 comments

Comments

@lin-xin
Copy link
Owner

lin-xin commented Sep 3, 2017

前言

Electron 是一个搭建跨平台桌面应用的框架,仅仅使用 JavaScript、HTML 以及 CSS,即可快速而容易地搭建一个原生应用。这对于想要涉及其他领域的开发者来说是一个非常大的福利。

项目介绍

仓库地址:lin-xin/calculator

我这里通过 Electron 实现了仿 iPhone 的计算器,通过菜单可以切换横屏和竖屏,横屏有更多的运算。而对于 JavaScript 进行浮点数计算来说,精度丢失是个很大问题,所以我这里使用了第三方库 math.js 来解决这个精度的问题。
尽可能的实现了跟 iPhone 一样的运算:

  • 1 + 2 × 3 = 7
  • 3 += 6 (再按 = 等于 9)
  • 0.1 + 0.2 = 0.3 (浮点数精度处理)

Image text
Image text

不过我下面并不是要讲计算器,而是用到的 Electron 的知识点。

生命周期

在主进程中通过 app 模块控制整个应用的生命周期。

当 Electron 完成初始化时触发 ready 事件:

app.on('ready', () => {
    // 创建窗口、加载页面等操作
})

当所有的窗口都被关闭时会触发 window-all-closed 事件:

app.on('window-all-closed', () => {
    if(process.platform !== 'darwin'){
        app.quit();     // 退出应用
    }
})

在开发中发现,没有监听该事件,打包后的应用关闭后,进程还保留着,会占用系统的内存。

窗口

本来我们的 html 只显示在浏览器中,而 electron 提供了一个 BrowserWindow 模块用于创建和控制浏览器窗口,我们的页面就是显示在这样的窗口中。

创建窗口

通过关键字 new 实例化返回 win 对象,该对象有丰富的方法对窗口进行控制。

win = new BrowserWindow({
    width: 390,         // 窗口宽度
    height: 670,        // 窗口高度
    fullscreen: false,  // 不允许全屏
    resizable: false    // 不允许改变窗口size,不然布局就乱了啊
});

加载页面

窗口创建完是一片空白的,可以通过 win.loadURL() 来加载要显示的页面。

const path = require('path');
const url = require('url');

win.loadURL(url.format({    // 加载本地的文件
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file',
    slashes: true
}))

也可以直接加载远程链接 win.loadURL('http://blog.gdfengshuo.com');

菜单

桌面应用菜单栏是最常见的功能。Electron 提供了 Menu 模块来创建原生的应用菜单和 context 菜单,

const template = [                              // 创建菜单模板
    {
        label: '查看',
        submenu: [
            {label: '竖屏', type: 'radio', checked: true},      // type 属性让菜单为 radio 可选
            {label: '横屏', type: 'radio', checked: false},
            {label: '重载',role:'reload'},
            {label: '退出',role:'quit'},
        ]
    }
]

const menu = Menu.buildFromTemplate(template);  // 通过模板返回菜单的数组
Menu.setApplicationMenu(menu);                  // 将该数组设置为菜单

在子菜单中,通过点击竖屏或横屏来进行一些操作,那就可以给 submenu 监听 click 事件。

const template = [
    {
        label: '查看',
        submenu: [
            {
                label: '横屏'
                click: () => {              // 监听横屏的点击事件
                    win.setSize(670,460);   // 设置窗口的宽高
                }
            }
        ]
    }
]

主进程和渲染进程通信

虽然点击横屏的时候,可以设置窗口的宽高,但是要如何去触发页面里的方法,这里就需要主进程跟渲染进程之间进行通信。

主进程,可以理解为 main.js 用来写 electron api 的就是主进程,渲染进程就是渲染出来的页面。

ipcMain

在主进程中可以使用 ipcMain 模块,它控制着由渲染进程(web page)发送过来的异步或同步消息。

const {ipcMain} = require('electron')
ipcMain.on('send-message', (event, arg) => {
    event.sender.send('reply-message', 'hello world')
})

ipcMain 监听 send-message 事件,当消息到达时可以调用 event.sender.send 来回复异步消息,向渲染进程发送 reply-message 事件,也可以带着参数发送过去。

ipcRenderer

在渲染进程可以调用 ipcRenderer 模块向主进程发送同步或异步消息,也可以收到主进程的相应。

const {ipcRenderer} = require('electron')
ipcRenderer.on('reply-message', (event, arg) => {
    console.log(arg);       // hello world
})

ipcRenderer.send('anything', 'hello everyone');

ipcRenderer 可以监听到来自主进程的 reply-message 事件并拿到参数进行操作,也可以使用 send() 方法向主进程发送消息。

webContents

webContents 是一个事件发出者,它负责渲染并控制网页,也是 BrowserWindow 对象的属性。在 ipcMain 中的 event.sender,返回发送消息的 webContents 对象,所以包含着 send() 方法用于发送消息。

const win = BrowserWindow.fromId(1);        // fromId() 方法找到ID为1的窗口
win.webContents.on('todo', () => {
    win.webContents.send('done', 'well done!')
})

remote

remote 模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径。在 Electron 中,有许多模块只存在主进程中,想要调用这些模块的方法需要通过 ipc 模块向主进程发送消息,让主进程调用这些方法。而使用 remote 模块,则可以在渲染进程中调用这些只存在于主进程对象的方法了。

const {remote} = require('electron')
const BrowserWindow = remote.BrowserWindow      // 访问主进程中的BrowserWindow模块

let win = new BrowserWindow();                  // 其他的跟主进程的操作都一样

remote 模块除了可以访问主进程的内置模块,自身还有一些方法。

remote.require(module)          // 返回在主进程中执行 require(module) 所返回的对象
remote.getCurrentWindow()       // 返回该网页所属的 BrowserWindow 对象
remote.getCurrentWebContents()  // 返回该网页的 WebContents 对象
remote.getGlobal(name)          // 返回在主进程中名为 name 的全局变量(即 global[name])
remote.process                  // 返回主进程中的 process 对象,等同于 remote.getGlobal('process') 但是有缓存

shell 模块

使用系统默认应用管理文件和 URL,而且在主进程和渲染进程中都可以用到该模块。在菜单中,我想点击子菜单打开一个网站,那么就可以用到 shell.openExternal() 方法,则会在默认浏览器中打开 URL

const {shell} = require('electron');
shell.openExternal('https://github.com/lin-xin/calculator');

打包应用

其实将程序打包成桌面应用才是比较麻烦的事。我这里尝试了 electron-packager 和 electron-builder。

electron-packager

electron-packager 可以将项目打包成各平台可直接运行的程序,而不是安装包。

先使用 npm 安装: npm install electron-packager -S

运行打包命令:

electron-packager ./ 计算器 --platform=win32 --overwrite --icon=./icon.ico

打包会把项目文件包括 node_modules 也一起打包进去,当然可以通过 --ignore=node_modules 来忽略文件,但是如果项目中有用到第三方库,忽略的话则找不到文件报错了。

正确的做法就是严格区分 dependencies 和 devDependencies,打包的时候只会把 dependencies 的库打包,而使用 cnpm 安装的会有一大堆 .0.xx@xxx 的文件,也会被打包,所以最好不要用 cnpm

electron-builder

electron-builder 是基于 electron-packager 打包出来的程序再做安装处理,将项目打包成安装文件。

安装:npm install electron-builder -S

打包:electron-builder --win

打包过程中,第一次下载 electron 可能会出现连接超时,可以使用 yarn 试试。还有 winCodeSign 和 nsis-resources 也可能会失败,可以参考 electron-builder/issues 解决。

总结

Electron 用起来还是相对容易的,可以创建个简单的桌面应用,只是打包的过程比较容易遇到问题,网上好像也有一键打包的工具,没尝试过。以上也都是基于 windows 7 的实践,毕竟没有 Mac 搞不了。

@ypinchina
Copy link

求推荐个学习electron的教程 明年要做这类项目

@lin-xin
Copy link
Owner Author

lin-xin commented Oct 24, 2017

@ypinchina 没看什么教程,看中文文档

Repository owner deleted a comment from iambtr May 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants