近期工作上有一個新的需求:開發一個桌面端小工具,用來將用戶端數據按照標準協議轉換并輸出。工作以來,B/S項目是主要工作內容,其他的基于console的工具、手機端app也有一些,但是GUI的開發還是首次。
1. 技術選型
因為前段時間用pandas做了一些數據處理,非常欣賞pandas這種數據處理、數據分析的能力,思考了下項目中對excel的處理轉換pandas都可以滿足,所以打算基于python來完成這個小項目。python的GUI技術/框架非常多,可以參考GUI Programming in Python.
1. 原生GUI框架
最開始考慮使用原生GUI,于是基于google做了一些初步的調研分析:
GUI技術 | 優點 | 缺點 |
---|---|---|
TKinter | 原生,安裝包小,性能好 | 容易上手,界面美化困難,代碼結構不好,組件少,功能少,只適合極簡的小工具 |
PyQT5 | 技術成熟,功能強大,多平臺通用,界面精美 | 復雜度高,學習成本高,商業付費,打包后程序幾百兆 |
PyGtk | 跟PyQt一樣,可以實現很不錯的效果,但是稍遜于PyQt,并且同樣有UI設計工具Glade | 更適合GNOME平臺 |
wxPython | 免費開源,提供了設計器,復雜度比PyQT5低,能力比TK強 | 設計器不夠強大,復雜界面不適合,界面美觀度不夠,跨平臺可能需要調試 |
PySimpleGUI | 基于Tkinter、Remi、wxPython和PyQt的封裝,文檔和示例豐富,封裝了api,開發效率高,適合短平快項目 | 封裝屏蔽了底層技術的實現細節,組件在各個底層技術上不全部通用 |
因為這個項目是一整套商業化平臺的配套工具,許可問題放棄PyQT5
;同時因為終端要支持mac和windows,PyGtk
不適合;放棄TK
是因為它的組件和功能滿足不了項目需求;剩下的wxPython
或者PySimpleGUI
大致是比較接近需求的方案,不過上面列出的缺點也是讓我有些顧慮。
2. 基于web的GUI實現
原生GUI框架避免不了的問題就是增加了額外的學習成本,Python的GUI實現也有一些基于web的實現框架,web的界面操作能力和開發效率都不是原生GUI能比的,如果能用web來解決需求,那當然是最合適的。初步調查結果如下:
技術 | 優點 | 缺點 |
---|---|---|
EEL | 非常輕量級,托管本地web服務器,實現python的js的互相調用 | 不原生支持flask等,需要系統提前安裝chrome;打包后文件大;右鍵菜單不能禁用; |
QWebview | - | 基于QT,許可問題 |
flaskwebgui | 支持flask、fastapi、django等框架 | 依賴chrome,活躍度低不夠成熟 |
PyWebview | 輕量級,基于系統自帶瀏覽器,啟動速度快,支持flask | 框架開放的接口有限,多平臺打包需要調試 |
electron | 基于nodejs,開發效率高,跨平臺,vscode高山仰止 | 體積和內存占用比較大,不支持xp,部分api非全平臺兼容,效率不如原生 |
調研過程中,被electron
種草,不過最終決定不改初衷,這個項目還是使用ython+PyWebview
實現;但是如果pywebview在使用中遇到比較嚴重的坑,也做好了隨時切換到nodejs+electron
的心理準備;中間也曾想過,使用python+nodejs+electron
來實現,這樣就兼顧到了electron和pandas,不過這樣做畢竟不太優雅,為了把nodejs和python黏合到一起,同時運行了兩套運行時,和一個額外的socket服務,不是第一選擇。
2. 開發
pywebview支持兩種方式集成html/js/css,一種是使用簡易的內置的web服務器,一種是使用第三方的web框架,比如flask。因為內置web服務器局限性比較大也不夠靈活,項目基于flask
搭建。前端UI框架因為考慮到兼容IE使用了layui
;數據處理使用pandas
;項目打包使用pyinstaller
。
項目部分結構如下:
biz
:python主體邏輯部分
biz/App.py
: flask應用構建和路由biz/Configs.py
: 項目配置,包括環境參數、窗口參數、日志等biz/JsAPI.py
: python數據處理類,暴露給前端js直接調用biz/WebAPI.py
: flask每條路由的處理方法類,前端可以通過ajax請求路由實現調用python的全部能力,加載web頁面時候使用此方法可以向前端傳遞一些必要參數biz/WindowHandler.py
: 處理window窗口事件,如on_closed、on_loadedstatic
: 放置項目用到的js、css、imgstemplates
: 放html頁面及組件main_mac.py
、main_win.py
: 項目入口,因為配置不一樣區分系統build.sh
、build.bat
: Pyinstaller腳本,打包app/exe
1) 使用virtualenv精簡打包依賴,給安裝包瘦身
2)webapi方法舉例,加載首頁時執行的方法:
def root(self):
"""
渲染 index.html
"""
logger.info('root', 'token:', webview.token)
width=config.get_config('window.width')
zoom=config.get_config('window.zoom')
return render_template('index.html', token=webview.token,width=width,zoom=zoom ,context={"name": "my_app"})
3) jsapi方法舉例:
def select_out_folder(self):
"""
選擇文件夾
"""
return self._window.create_file_dialog(webview.FOLDER_DIALOG)
3. pywebview打包
pyinstaller打包在mac上基本上比較順利,但是在windows上出現了一些問題,記錄如下:
1) 打包腳本
pyinstaller打包windows exe時,相比mac上的腳本需要引入
WebBrowserInterop.x64.dll
,腳本如下:
pyinstaller src/main_win.py -F -w -y ^
--paths "src" ^
--name="my_app" ^
--add-data="venv/lib/site-packages/webview/lib/WebBrowserInterop.x64.dll;webview/lib" ^
--add-data="src/static;static" ^
--add-data="src/templates;templates" ^
--add-data="src/log;log" ^
--icon="icon.ico" ^
--clean
2) clrModule.PyInit_clr()
異常
因為mac上基于python3.9開發,所以在windows上搭建了同樣的環境;但是pythonnet不支持python3.9導致該問題;將python降級到3.8,問題解決。
3)ERROR:icu_util.cc(133)] Invalid file descriptor to ICU data received.
在windows平臺將
gui=cef
修改成mshtml
;考慮到兼容性和客戶端的多樣性,windows安裝包使用mshtml
是最保險的。
4) 在windows平臺上使用js調用JsAPI方法時,語法報錯
IE11不支持promise語法,引入
blurbird.js
,修改原api中promise的實現方式,具體如下:
// 原API的promise調用方式
js_api.select_excel_file().then(response => {
}).catch(err => {
console.error(err)
})
// 基于blurbird.js實現方式
js_api.select_excel_file().then(response => {
},err => {
console.error(err)
})
5) windows下窗口大小不包括標題欄、滾動條
打包時根據平臺使用兩份配置,windows窗口額外增加標題欄高度和滾動條寬度
6)在windows平臺,當html使用了scale縮放后,窗口在debug模式和非debug模式大小不一致
該問題暫時通過配置特殊處理walk around
7)windows下點擊關閉按鈕,主進程卡死(not responding)
pywebview的一個bug,在mac平臺,不執行window的
on_closed
事件,關閉窗口需要在on_closing
事件中執行window.destroy()
;
但是在windows平臺,在on_closing
事件中執行window.destroy()
會導致主進程卡死;windows平臺只能在on_closed
事件關閉窗口