Python 開(kāi)發(fā)GUI要么太繁瑣要么太丑,,而前端技術(shù)恰巧是最適合做漂亮UI的,。所以考慮將Python和前端技術(shù)結(jié)合,,通過(guò)進(jìn)程通信和前端框架交流,,打包成一個(gè)完整的桌面APP,。教程分成兩種實(shí)現(xiàn)方式,,一個(gè)是zerorpc進(jìn)程通信一個(gè)是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基礎(chǔ) 詳情見(jiàn)官方文檔: https://electronjs.org/docs
如果你可以建一個(gè)網(wǎng)站,,你就可以建一個(gè)桌面應(yīng)用程序。 Electron 是一個(gè)使用 JavaScript, HTML 和 CSS 等 Web 技術(shù)創(chuàng)建原生程序的框架,,它負(fù)責(zé)比較難搞的部分,,你只需把精力放在你的應(yīng)用的核心上即可。
有很多有名的App是用Electeon開(kāi)發(fā)的,,如:Skype和GitHub以及著名編輯器Atom,,所以這個(gè)框架在水平上是被認(rèn)可的。
Electron 可以讓你使用純 JavaScript 調(diào)用豐富的原生(操作系統(tǒng)) APIs 來(lái)創(chuàng)造桌面應(yīng)用。 你可以把它看作一個(gè)專(zhuān)注于桌面應(yīng)用的 Node. js 的變體,,而不是 Web 服務(wù)器,。 這不意味著 Electron 是某個(gè)圖形用戶(hù)界面(GUI)庫(kù)的 JavaScript 版本。 相反,,Electron 使用 web 頁(yè)面作為它的 GUI,,所以你能把它看作成一個(gè)被 JavaScript 控制的,精簡(jiǎn)版的 Chromium 瀏覽器,。 安裝為你的新Electron應(yīng)用創(chuàng)建一個(gè)新的空文件夾,。 打開(kāi)你的命令行工具,然后從該文件夾運(yùn)行npm init ,。將package.json 修改一下,。 { 'name': 'your-app', 'version': '0.1.0', 'main': 'main.js', 'scripts': { 'start': 'electron .' } }
現(xiàn)在,您需要安裝electron ,。 我們推薦的安裝方法是把它作為您 app 中的開(kāi)發(fā)依賴(lài)項(xiàng),,這使您可以在不同的 app 中使用不同的 Electron 版本。 在您的app所在文件夾中運(yùn)行下面的命令: npm install --save-dev electron
使用electron 模塊包含了Electron提供的所有API和功能,,引入方法和普通Node.js模塊一樣:
const electron = require('electron')
electron 模塊所提供的功能都是通過(guò)命名空間暴露出來(lái)的,。 比如說(shuō): electron.app 負(fù)責(zé)管理Electron 應(yīng)用程序的生命周期, electron.BrowserWindow 類(lèi)負(fù)責(zé)創(chuàng)建窗口,。 下面是一個(gè)簡(jiǎn)單的main.js 文件,,它將在應(yīng)用程序準(zhǔn)備就緒后打開(kāi)一個(gè)窗口:
const {app, BrowserWindow} = require('electron') function createWindow () { // 創(chuàng)建瀏覽器窗口 win = new BrowserWindow({width: 800, height: 600}) // 然后加載應(yīng)用的 index.html。 win.loadFile('index.html') } app.on('ready', createWindow)
您應(yīng)當(dāng)在 main.js 中創(chuàng)建窗口,,并處理程序中可能遇到的所有系統(tǒng)事件,。 下面我們將完善上述例子,添加以下功能:打開(kāi)開(kāi)發(fā)者工具,、處理窗口關(guān)閉事件,、在macOS用戶(hù)點(diǎn)擊dock上圖標(biāo)時(shí)重建窗口,添加后,,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 () { // 創(chuàng)建瀏覽器窗口,。 win = new BrowserWindow({width: 800, height: 600}) // 然后加載應(yīng)用的 index.html。 win.loadFile('index.html') // 打開(kāi)開(kāi)發(fā)者工具 win.webContents.openDevTools() // 當(dāng) window 被關(guān)閉,,這個(gè)事件會(huì)被觸發(fā),。 win.on('closed', () => { // 取消引用 window 對(duì)象,如果你的應(yīng)用支持多窗口的話,, // 通常會(huì)把多個(gè) window 對(duì)象存放在一個(gè)數(shù)組里面,, // 與此同時(shí),你應(yīng)該刪除相應(yīng)的元素,。 win = null }) } // Electron 會(huì)在初始化后并準(zhǔn)備 // 創(chuàng)建瀏覽器窗口時(shí),,調(diào)用這個(gè)函數(shù),。 // 部分 API 在 ready 事件觸發(fā)后才能使用。 app.on('ready', createWindow) // 當(dāng)全部窗口關(guān)閉時(shí)退出,。 app.on('window-all-closed', () => { // 在 macOS 上,,除非用戶(hù)用 Cmd + Q 確定地退出, // 否則絕大部分應(yīng)用及其菜單欄會(huì)保持激活,。 if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { // 在macOS上,,當(dāng)單擊dock圖標(biāo)并且沒(méi)有其他窗口打開(kāi)時(shí), // 通常在應(yīng)用程序中重新創(chuàng)建一個(gè)窗口,。 if (win === null) { createWindow() } }) // 在這個(gè)文件中,,你可以續(xù)寫(xiě)應(yīng)用剩下主進(jìn)程代碼。 // 也可以拆分成幾個(gè)文件,,然后用 require 導(dǎo)入,。
最后,創(chuàng)建你想展示的 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>
啟動(dòng)在創(chuàng)建并初始化完成 main.js ,、 index.html 和package.json 之后,,您就可以在當(dāng)前工程的根目錄執(zhí)行 npm start 命令來(lái)啟動(dòng)剛剛編寫(xiě)好的Electron程序了。
Python部分安裝pip install zerorpc ,。在項(xiàng)目根目錄創(chuàng)建文件夾py ,,用于存放Python相關(guān)代碼。新建一個(gè)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()
然后命令行里運(yùn)行python api.py ,。再另一個(gè)終端輸入zerorpc tcp://localhost:4242 hello NXB ,如果得到Hello,NXB 則沒(méi)有問(wèn)題。 Electron部分接著之前的main.js 后面寫(xiě),。我們首先需要node.js能夠調(diào)用Python進(jìn)程,。 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)
寫(xiě)完后運(yùn)行npm start 看看能不能啟動(dòng)python子程序按照之前的方式測(cè)試一下能不能通信。沒(méi)問(wèn)題的話繼續(xù),。 修改我們的主頁(yè)index.html ,,構(gòu)建一個(gè)輸入框。我們希望在輸入框里輸入字符,,下方可以動(dòng)態(tài)顯示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>
在根目錄下創(chuàng)建render.js 用于監(jiān)聽(tīng)輸入框,將輸入框的內(nèi)容動(dòng)態(tài)發(fā)送給python進(jìn)程,,并接收反回來(lái)的消息,。 // 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'))
如果沒(méi)問(wèn)題的話應(yīng)該是這樣的效果:
打包測(cè)試沒(méi)問(wèn)題之后我們需要將應(yīng)用打包,因?yàn)閯e人電腦上不一定裝了node.js或是python,。首先要裝個(gè)打包工具pip install pyinstaller ,。 在package.json 的script 中加入'build-python':'pyinstaller ./py/api.py --clean --distpath ./pydist' ,。然后運(yùn)行npm run build-python編譯一下 。編譯完了可以把根目錄下生成的build 文件夾和api.spec 刪了,。如果中間報(bào)錯(cuò) AttributeError: module 'enum' has no attribute 'IntFlag' ,,就運(yùn)行pip uninstall enum34 把enum34刪了。 This is likely caused by the package
enum34 . Since python 3.4 there’s a standard library
enum module, so you should uninstall
enum34 , which is no longer compatible with the enum in the standard library since
enum.IntFlag was added in python 3.6
之前子進(jìn)程是通過(guò)調(diào)用python命令運(yùn)行的,,現(xiàn)在我們要換成生成的可執(zhí)行程序,。修改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])
運(yùn)行npm start 可以查看效果。 在根目錄運(yùn)行npm install electron-packager --save-dev 安裝Electron打包模塊,。然后將'pack-app': './node_modules/.bin/electron-packager . --overwrite --ignore=py$' 寫(xiě)入package.json 的script中,。 運(yùn)行npm run pack-app 打包程序。最后會(huì)生成可執(zhí)行文件,,復(fù)制到別的電腦也可以運(yùn)行,。
nofacer/Python_GUI_with_Electron? github.com
|