Electron Python界面开发(通过zerorpc)
Python 开发GUI要么太繁琐要么太丑,而前端技术恰巧是最适合做漂亮UI的。所以考虑将Python和前端技术结合,通过进程通信和前端框架交流,打包成一个完整的桌面APP。教程分成两种实现方式,一个是zerorpc进程通信一个是http通信。
这篇教程介绍zerorpc的方式,流程如下:
start | V+--------------------+| | start| electron +-------------> +------------------+| | sub process | || (browser) | | python server || | | || (all html/css/js) | | (business logic) || | zerorpc | || (node.js runtime, | <-----------> | (zeromq server) || zeromq client) | communication | || | | |+--------------------+ +------------------+Electron基础
详情见官方文档:
https://electronjs.org/docs
如果你可以建一个网站,你就可以建一个桌面应用程序。 Electron 是一个使用 JavaScript, HTML 和 CSS 等 Web 技术创建原生程序的框架,它负责比较难搞的部分,你只需把精力放在你的应用的核心上即可。
有很多有名的App是用Electeon开发的,如:Skype和GitHub以及著名编辑器Atom,所以这个框架在水平上是被认可的。
Electron 可以让你使用纯 JavaScript 调用丰富的原生(操作系统) APIs 来创造桌面应用。 你可以把它看作一个专注于桌面应用的 Node. js 的变体,而不是 Web 服务器。
这不意味着 Electron 是某个图形用户界面(GUI)库的 JavaScript 版本。 相反,Electron 使用 web 页面作为它的 GUI,所以你能把它看作成一个被 JavaScript 控制的,精简版的 Chromium 浏览器。
安装
为你的新Electron应用创建一个新的空文件夹。 打开你的命令行工具,然后从该文件夹运行npm init。将package.json修改一下。
{ 'name': 'your-app', 'version': '0.1.0', 'main': 'main.js', 'scripts': { 'start': 'electron .' } }
现在,您需要安装electron。 我们推荐的安装方法是把它作为您 app 中的开发依赖项,这使您可以在不同的 app 中使用不同的 Electron 版本。 在您的app所在文件夹中运行下面的命令:
npm install --save-dev electron使用
electron模块包含了Electron提供的所有API和功能,引入方法和普通Node.js模块一样:
const electron = require('electron')
electron 模块所提供的功能都是通过命名空间暴露出来的。 比如说: electron.app负责管理Electron 应用程序的生命周期, electron.BrowserWindow类负责创建窗口。 下面是一个简单的main.js文件,它将在应用程序准备就绪后打开一个窗口:
const {app, BrowserWindow} = require('electron') function createWindow () { // 创建浏览器窗口 win = new BrowserWindow({width: 800, height: 600}) // 然后加载应用的 index.html。 win.loadFile('index.html') } app.on('ready', createWindow)您应当在 main.js 中创建窗口,并处理程序中可能遇到的所有系统事件。 下面我们将完善上述例子,添加以下功能:打开开发者工具、处理窗口关闭事件、在macOS用户点击dock上图标时重建窗口,添加后,main. js 就像下面这样:
const {app, BrowserWindow} = require('electron') // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let win function createWindow () { // 创建浏览器窗口。 win = new BrowserWindow({width: 800, height: 600}) // 然后加载应用的 index.html。 win.loadFile('index.html') // 打开开发者工具 win.webContents.openDevTools() // 当 window 被关闭,这个事件会被触发。 win.on('closed', () => { // 取消引用 window 对象,如果你的应用支持多窗口的话, // 通常会把多个 window 对象存放在一个数组里面, // 与此同时,你应该删除相应的元素。 win = null }) } // Electron 会在初始化后并准备 // 创建浏览器窗口时,调用这个函数。 // 部分 API 在 ready 事件触发后才能使用。 app.on('ready', createWindow) // 当全部窗口关闭时退出。 app.on('window-all-closed', () => { // 在 macOS 上,除非用户用 Cmd + Q 确定地退出, // 否则绝大部分应用及其菜单栏会保持激活。 if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { // 在macOS上,当单击dock图标并且没有其他窗口打开时, // 通常在应用程序中重新创建一个窗口。 if (win === null) { createWindow() } }) // 在这个文件中,你可以续写应用剩下主进程代码。 // 也可以拆分成几个文件,然后用 require 导入。
最后,创建你想展示的 index.html:
<!DOCTYPE html> <html> <head> <meta charset='UTF-8'> <title>Hello World!</title> </head> <body> <h1>Hello World!</h1> We are using node <script>document.write(process.versions.node)</script>, Chrome <script>document.write(process.versions.chrome)</script>, and Electron <script>document.write(process.versions.electron)</script>. </body> </html>启动
在创建并初始化完成 main.js、 index.html和package.json之后,您就可以在当前工程的根目录执行 npm start 命令来启动刚刚编写好的Electron程序了。
Python部分
安装pip install zerorpc。在项目根目录创建文件夹py,用于存放Python相关代码。新建一个python文件,命名为api.py。敲入如下demo。
import zerorpcclass HelloRPC(object): def hello(self, name): return 'Hello, %s' % names = zerorpc.Server(HelloRPC())s.bind('tcp://0.0.0.0:4242')s.run()
然后命令行里运行python api.py。再另一个终端输入zerorpc tcp://localhost:4242 hello NXB,如果得到Hello,NXB则没有问题。
Electron部分
接着之前的main.js后面写。我们首先需要node.js能够调用Python进程。
const path=require('path')let pyProc = nulllet pyPort = nullconst createPyProc = () => { let port = '4242' let script = path.join(__dirname, 'py', 'api.py') pyProc = require('child_process').spawn('python', [script, port]) if (pyProc != null) { console.log('child process success') }}const exitPyProc = () => { pyProc.kill() pyProc = null pyPort = null}app.on('ready', createPyProc)app.on('will-quit', exitPyProc)写完后运行npm start看看能不能启动python子程序按照之前的方式测试一下能不能通信。没问题的话继续。
修改我们的主页index.html,构建一个输入框。我们希望在输入框里输入字符,下方可以动态显示Hello,XX。
<!-- index.html --><!DOCTYPE html><html> <head> <meta charset='UTF-8'> <title>Hello XX</title> </head> <body> <input id='name' ></input> <p id='result' color='black'></p> </body> <script> require('./render.js') </script></html>
在根目录下创建render.js用于监听输入框,将输入框的内容动态发送给python进程,并接收反回来的消息。
// renderer.js var zerorpc = require('zerorpc');var client = new zerorpc.Client();client.connect('tcp://127.0.0.1:4242');let name = document.querySelector('#name')let result = document.querySelector('#result')name.addEventListener('input', () => { client.invoke('hello', name.value, (error, res) => { if(error) { console.error(error) } else { result.textContent = res } })})name.dispatchEvent(new Event('input'))如果没问题的话应该是这样的效果:
打包
测试没问题之后我们需要将应用打包,因为别人电脑上不一定装了node.js或是python。首先要装个打包工具pip install pyinstaller。
在package.json的script中加入'build-python':'pyinstaller ./py/api.py --clean --distpath ./pydist'。然后运行npm run build-python编译一下。编译完了可以把根目录下生成的build文件夹和api.spec删了。如果中间报错 AttributeError: module 'enum' has no attribute 'IntFlag',就运行pip uninstall enum34把enum34删了。
This is likely caused by the package
enum34. Since python 3.4 there’s a standard library
enummodule, so you should uninstall
enum34, which is no longer compatible with the enum in the standard library since
enum.IntFlagwas added in python 3.6
之前子进程是通过调用python命令运行的,现在我们要换成生成的可执行程序。修改main.js:
// let script = path.join(__dirname, 'py', 'api.py') // pyProc = require('child_process').spawn('python', [script, port]) let script = path.join(__dirname, 'pydist', 'api','api') pyProc = require('child_process').execFile(script, [port])
运行npm start可以查看效果。
在根目录运行npm install electron-packager --save-dev安装Electron打包模块。然后将'pack-app': './node_modules/.bin/electron-packager . --overwrite --ignore=py$'写入package.json的script中。
运行npm run pack-app打包程序。最后会生成可执行文件,复制到别的电脑也可以运行。
nofacer/Python_GUI_with_Electron github.com
