创建第一个项目
最近在做一个流程图编辑工具,已经使用mxGraph实现了线上版本,现在希望做一个本地版本,于是想到了Electron,看一下是不是可以封装一下,直接使用。由于没有用过Electron,所以还是老实点,从头开始,免得掉坑。
首先是创建环境,使用npm进行安装,按照官网得说明,很顺利就安装完成了,输入electron -v看一下版本,v8.0.2,说明安装没有问题。接下来按官网得说明创建一个简单得应用,就三个文件:package.json,index.html,main.js。package.json声明了应用的名称、版本号和主程序入口:
1 2 3 4 5
| { "name" : "my-graph-editor", "version" : "0.1.0", "main" : "main.js" }
|
main.js创建了electron的主进程,并且调入index.html,剩下的事情就交给渲染进程了。对于大部分工作通过js完成的应用来说,main.js的代码几乎都是相同的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const {app, BrowserWindow} = require('electron') //声明窗体变量 let graphWin;
//当app完成初始化时,执行窗体的创建。 app.on('ready', createWindow)
function createWindow(){ //构建一个高800,宽1200的窗体。 graphWin = new BrowserWindow({width: 1200, height: 800, //设置支持node集成 webPreferences: { nodeIntegration: true } }) //窗体中显示的内容是index.html文件中的内容,就是所谓的渲染进程,可以认为在内嵌的google浏览器中执行 //__dirname,表示main.js所在的目录路径 graphWin.loadURL(__dirname + "/index.html") //当窗体已经关闭时,将win赋值为null。 graphWin.on('closed', () => { graphWin= null }) }
|
这时,在应用目录中执行
第一个应用就执行了,由于index.html中没有内容,所以有一个空的窗口。试着把在线可以执行的js代码拷贝到Lib\mxGraph目录,并将js引入、初始化等代码拷贝到index.html,试着执行一下,居然成功了。当然,还有很多问题需要解决,文件保存和打开、菜单项的操作等等,但相比从头开始一个做一个桌面应用,已经简单很多了。
打开和保存本地文件
在线编辑流程图的工具通过Ajax访问服务器的文件服务实现打开和保存流程图,在本地应用时,我们需要使用node.js的文件服务来实现这个功能。我们编写一个简单的应用来测试打开和保存本地文件功能。
前面已经提到了,为了使用node.js的功能,需要在main.js初始化窗体时声明nodeIntegration: true:
1 2 3 4 5 6
| graphWin = new BrowserWindow({width: 1200, height: 800, //设置支持node集成 webPreferences: { nodeIntegration: true } })
|
然后,我们需要声明使用node.js的文件服务:
接下来就可以调用文件服务读写本地的文件了:
1 2 3 4 5 6 7 8 9
| fs.readFile(filename, 'utf8', function (err, data) { console.log(data); });
fs.writeFile(filename, content, function (err) { if(err) alert(err); else alert("保存成功"); });
|
使用打开文件对话框和保存文件对话框选择文件路径
上一节我们使用node.js的文件服务打开和保存文件,现在我们看如何使用electron的对话框来选择本地文件。可以在主进程操作electron的对话框,如果在渲染进程操作,需要使用remote访问主进程,代码是这样的:
1
| const {dialog} = require('electron').remote;
|
然后,就可以启动对话框选择文件了,启动打开对话框的代码如下:
1 2 3 4 5 6 7 8 9 10 11
| let options = { title : "打开文件", defaultPath : "", buttonLabel : "打开", filters :[ {name: '文本文件', extensions: ['txt']}, {name: 'All Files', extensions: ['*']} ], properties: ['openFile'] } let files=dialog.showOpenDialogSync( options);
|
这里使用的是同步打开,如果使用异步方法,可以使用showOpenDialog,后面加回调函数。还有需要注意的是返回的是数组,下面是打开文件的代码:
1 2 3 4
| if(file&&file.length>0) fs.readFile(file[0], 'utf8', function (err, data) { console.log(data); });
|
保存文件与打开文件类似,只是返回的是文件路径,不是数组了。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let file=dialog.showSaveDialogSync( { filters :[ {name: 'Text', extensions: ['txt']}, {name: 'All Files', extensions: ['*']} ], properties: ['saveFile'] } ); console.log(file); if(file) fs.writeFile(file, txt.value, function (err) { if(err) alert(err); else alert("保存成功"); });
|
菜单
我们现在希望将Electron应用的缺省菜单替换为我们自己的菜单,然后通过菜单实现打开文件和写入文件的功能。调用菜单也需要访问主进程:
1 2
| const { remote } = require('electron') const { Menu, MenuItem } = remote
|
然后可以定义新的菜单:
使用append添加新的菜单项目,菜单项中label定义了菜单的显示标签,role定义了菜单的缺省功能,比如role:’quit’说明菜单执行退出功能。在菜单项的submenu中定义子菜单。通过click事件自定义菜单功能。
Index.html的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="content"></div>
<input type="text" id="txtContent" />
</body> <script> var content = document.getElementById('content'); var txt=document.getElementById('txtContent'); var fs = require("fs"); const {dialog} = require('electron').remote; const { remote } = require('electron') const { Menu, MenuItem } = remote const menu = new Menu() menu.append(new MenuItem( { label: '文件', role:'filemenu', submenu:[ { label:'打开', click() { let options = { title : "打开文件", defaultPath : "", buttonLabel : "打开", filters :[ {name: '文本文件', extensions: ['txt']}, {name: 'All Files', extensions: ['*']} ], properties: ['openFile'] } let files=dialog.showOpenDialogSync( options); if(file&&file.length>0) fs.readFile(file[0], 'utf8', function (err, data) { content.innerText = data; }); } }, { label:'保存', click() { let file=dialog.showSaveDialogSync( { filters :[ {name: 'Text', extensions: ['txt']}, {name: 'All Files', extensions: ['*']} ], properties: ['saveFile'] } ); if(file) fs.writeFile(file, txt.value, function (err) { if(err) alert(err); else alert("保存成功"); }); } }, {label:'退出',role:'quit'} ] })) Menu.setApplicationMenu(menu) </script> </html>
|