引言
pip命令行工具在平常使用方面确实足够简单,本项目只是作为TinUI多界面开发的示例。
当然,总有人想用GUI版pip,实际上也有。不过现在,我们就来手搓一个基于python和TinUI(tkinter)的pip可视化管理器。
命名为“pip集合功能平台”:Pip Integration Platform(PIP)
没错,我就是故意的。
项目开源地址:Github · Pip Integration Platform。
文件结构
│ gui.py 界面控制
│ main.py 主程序
│ pipmode.py pip功能控制
│ TinUI.py UI支持
│
├─lib
│ ├─gui 三个界面
│ │ │ gui_install.py
│ │ │ gui_list.py
│ │ │ gui_uninstall.py
│ │
│ └─operate pip功能的具体实现
│ │ pip_install.py
│ │ pip_list.py
│ │ pip_uninstall.py
│
├─pages 页面设计
│ main.xml
│ p1_libs.xml
│ p2_install.xml
│ p4_uninstall.xml
结构说明见PIP程序结构。
页面设计
无论最终结果是什么样,先把能够看的搭建起来。本项目的四个界面均使用TinUI库自带的TinUIXml编辑器。
main.xml
为简单的标签页控件,这里不展示。
p1_libs.xml
<!--TinUIXml编辑器-->
<tinui><line><listbox width='760' height='460' data='("TinUI",)' command='self.funcs["sel_libs"]'>lsbox</listbox></line><line><button2 text='打开文件位置' command='self.funcs["opendoc"]'></button2><button2 text='打开项目页面' command='self.funcs["pypidoc"]'></button2><button2 text='卸载' command='self.funcs["uninstall"]'></button2><button text='检测全部可更新项目' command='self.funcs["update"]'></button></line>
</tinui>
p2_install.xml
<!--TinUIXml编辑器-->
<tinui><line y='20' anchor='w'><paragraph text='第三方库名:'></paragraph><entry width='300'>entry</entry><checkbutton text='升级' command='self.funcs["update_switch"]'>check</checkbutton><button2 text='开始安装' command='self.funcs["install"]'>button</button2></line><line><textbox width='760' height='480' scrollbar='True'>textbox</textbox></line>
</tinui>
p4_uninstall.xml
<!--TinUIXml编辑器-->
<tinui><line y='20' anchor='w'><paragraph text='要卸载的库:'></paragraph><entry width='300'>entry</entry><button2 text='开始卸载' command='self.funcs["uninstall2"]'>button</button2></line><line><textbox width='760' height='480' scrollbar='True'>textbox</textbox></line>
</tinui>
界面交互
PIP的总界面管理见gui.py。
各项功能界面见PIP的lib.gui.*。
子线程运行pip命令
因为pip命令大多为耗时命令,因此在PIP中,pip命令将被放置在子线程中运行,在运行结束后会调用回调函数并触发窗口事件,结束子线程并在界面线程中展示操作结果。
这种做法不仅不会阻碍界面线程,还能够在界面线程实时显示命令行输出信息。以安装(install)功能为例。
gui_install.py
#...update=False#是否升级,用于调整pip参数
update_page=False#升级检测页面是否打开
update_page_id=None#升级检测页面对应到TinUI.notebook的页面id
book=None#标签页控件
ui=None#标签页中对应的BasicTinUI#...
def install():#开始下载(执行pip命令,不判断正误)name=entry.get()entry.disable()check.disable()button.disable()pipmode.install(update,name,add_msg,end)def add_msg(_msg:str):#接受pip_install的信息global msgmsg=_msgtextbox.event_generate('<<NewMsg>>')
def _add_msg(e):#接受pip_install调用add_msg传递的信息textbox.config(state='normal')textbox.insert('end',msg)textbox.see('end')textbox.config(state='disabled')def end():#接受pip_install停止操作textbox.event_generate('<<End>>')
def _end(e):#操作结束,按钮恢复entry.normal()check.active()button.active()textbox.config(state='normal')textbox.insert('end','====================\n\n')textbox.config(state='disabled')#...
pip_install.py
"""
/lib/operate/pip_install.py
升级和安装的第三方库
"""
import subprocess
import threadingdef __install(update,name,msgfunc,endfunc):if update:#已安装,升级cmd="pip install --upgrade "+nameelse:#安装cmd="pip install "+namemsgfunc(cmd+'\n')result=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,shell=True)for line in iter(result.stdout.readline, b''):msgfunc(line.decode('utf-8'))endfunc()def install(update,name,msgfunc,endfunc):thread = threading.Thread(target=__install,args=(update,name,msgfunc,endfunc,))thread.setDaemon(True)thread.start()
可以看到,界面按钮通过pipmode.py
调用了lib.operate.pip_install
的install
方法,创建了一个名为thread
的线程并在其中运行pip命令。
我们注意__install
方法中,存在msgfunc(...)
和endfunc(...)
的回调,再看看gui_install.py
中对应的add_msg(...)
和end(..)
方法。我们着重看add_msg
方法,其中只进行了两步,第一步是将返回值变为全局变量,本进程公用;第二步触发界面的虚拟事件<<NewMsg>>
,后续步骤脱离子线程,而子线程一直运行,直到回调endfunc(...)
。
这个时候,因为虚拟事件被触发,gui_install.py
在主线程开始运行_add_msg
方法,实现对信息流的GUI展示。
各项pip功能见PIP的lib.operate.*。
子线程回调见PIP的多线程回调方式。
效果
初始化
安装
更新
检测更新是一个漫长的过程。
返回库列表点击按钮只是为了展示界面线程运行正常。
卸载
结语
这就是我们手搓的一个简易pip功能集合平台。
详细内容见开源项目地址:Github · Pip Integration Platform。