近期工作上有一个新的需求:开发一个桌面端小工具,用来将用户端数据按照标准协议转换并输出。工作以来,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
事件关闭窗口