如果你接触过桌面 GUI 软件开发,那么你一定会对 MFC、WPF、Qt 等有或多或少的了解。
那么什么是 GUI 软件呢?GUI 软件是带有用户交互界面的软件,有按钮,有窗口,还有很多其它用于和用户交互的小部件。常见的例子是,比如各个厂家推出的浏览器,上面有标签,有按钮,有网址输入栏,有网页内容展示窗口和状态栏等等。
不过,这里打算为大家介绍一下使用 Python 怎么去做 GUI 开发,因为会 Python 的人更多,相信其中也有很多人对开发界面软件颇有兴趣。好,看下文。
Python 的 GUI 开发框架有好几种,比较主流的有 wxPython、PyQt、Tkinter,这三种都是跨平台方案。
wxPython 是 Python 的第三方库,代码实现基于 C++ 的 wxWidgets 库封装,呈现的界面风格和系统本地风格一致。其许可协议规定如果直接引用 wxPython 的二进制库文件,则可以随便使用。
可以看看在 linux 系统下基于 wxPython 的界面程序是什么样子

PyQt 是 Qt 平台的 Python 版本,自绘的界面风格,许可协议对商业应用不太友好。虽然和原生风格已经很接近了,但还是有人能挑出刺来。
可以看看在 linux 系统下基于 PyQt 的界面程序是什么样子

Tkinter 是 Python 自带的 GUI 开发框架,也是本地化风格。
看看在 linux 系统下基于 Tkinter 的界面,风格真的有些过时了

值得一提的是,wxPython 也是 Python 作者主推的 GUI 开发框架。
今天先讲 wxPython,至于其它方案,会在后边的其它推文讲解,敬请关注。
环境配置
由于 VSCODE 的开放性和插件生态极其丰富的原因,推荐基于 VSCODE 来搭建开发环境。
本文以下内容基于 windows 10 和 Python3.
开发 Python 工程之前,先配置一个虚拟环境。
在 VSCODE 里打开选好的工程存放目录,然后点击 VSCODE 顶部菜单栏 Terminal -> New Terminal,弹出命令行终端,输入
python -m venv .venv
启动虚拟环境
.venv\Scripts\activate.bat
安装 wxPython
先查看一下当前环境里已经预装了哪些工具包
pip list
Output:
Package Version
---------- -------
pip 21.1.1
setuptools 56.0.0
WARNING: You are using pip version 21.1.1; however, version 22.3.1 is available.
You should consider upgrading via the '.\.venv\scripts\python.exe -m pip install --upgrade pip' command.
建议升级一下 pip
python -m pip install --upgrade pip
Output:
Requirement already satisfied: pip in .\.venv\lib\site-packages (21.1.1)
Collecting pip
Using cached pip-22.3.1-py3-none-any.whl (2.1 MB)
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 21.1.1
Uninstalling pip-21.1.1:
Successfully uninstalled pip-21.1.1
Successfully installed pip-22.3.1
安装 wxPython 包
pip install wxpython
Output:
Collecting wxpython
Using cached wxPython-4.2.0-cp38-cp38-win_amd64.whl (18.0 MB)
Collecting pillow
Using cached Pillow-9.3.0-cp38-cp38-win_amd64.whl (2.5 MB)
Collecting numpy
Using cached numpy-1.23.5-cp38-cp38-win_amd64.whl (14.7 MB)
Collecting six
Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: six, pillow, numpy, wxpython
Successfully installed numpy-1.23.5 pillow-9.3.0 six-1.16.0 wxpython-4.2.0
再查看一下实际都安装了哪些工具包
Package Version
---------- -------
numpy 1.23.5
Pillow 9.3.0
pip 22.3.1
setuptools 56.0.0
six 1.16.0
wxPython 4.2.0
看到,wxpython 其实还依赖了 numpy、Pillow、six 这几个包。
基本程序
先来看一看一个基本的 wxPython GUI 程序骨架,新建文件 main.py,输入以下内容并保存。
// main.py
import wx
app = wx.App()
window = wx.Frame(parent=None, title="hello Python GUI APP!")
window.Show()
app.MainLoop()
导入 wx 库,也就是 wxPython 库,首先实例化类 wx.App。
wx.App 类代表着整个应用,被用于引导 wxPython 系统,初始化底层的 gui 工具包,设置或者获取应用级别的属性,实现本机窗口系统的主消息或者事件环,以及分派事件到各个窗口实例等。每个 wx 应用都必须有且仅有一个 wx.App 类实例,而且为了确保 gui 平台和小部件都被初始化完成,所有 UI 对象的创建必须在 wx.App 类实例创建之后。为了实现更复杂的初始化过程,可以对 wx.App 类执行派生,然后重写方法 OnInit,窗口初始化完成时会调用方法 OnInit。
然后,创建 wx.Frame 类实例,初始化时,设置父窗口为空和设置标题为 hello Python GUI APP!。
wx.Frame 是用户可以改变大小和位置的窗口类,调用 Show() 显示窗口。
wxPython 提供了非常丰富的窗口部件(widget)和辅助控件来简化复杂 GUI 的开发过程。基本的 widget 比如有输入框 TextCtrl,按键 Button,静态文本 StaticText,菜单栏 MenuBar,列表 ListCtrl等。简单的辅助控件比如有布局器 BoxSizer,菜单 Menu等。为了显示丰富的内容,除了可以直接使用框架提供的窗口部件,还可以对这些部件派生以添加更多功能。
最后,调用类 wx.App 对象的 MainLoop() 方法来启动主界面的事件环,这时候用户和界面交互才会有反应。
输入指令以执行程序
python main.py

通过显示的程序窗口,可看到整个程序只做了很基本的 UI 窗口显示,但是代码结构也相对简单。如果需要实现更复杂的窗口界面呢?往下看。
使用 wxFormBuilder 设计复杂 UI
从上一节的 demo 代码中,看到要实现显示一个窗口是很简单的,但是如果窗口内部包含了很多其它部件呢?
继续手堆代码吗?不是不可以,但是这样子的做法对工程或者项目后期维护是很不利的。
那么怎么办?很巧,wxWidgets 框架提供了一个 GUI 构建器 wxFormBuilder。

打开 wxFormBuilder,可以看到提供了非常多的工具,而且自动创建一个空白工程。为了设计需要的界面,开发者可以通过手动点击组件面板(Component Palette)中不同种类的各种控件来布局界面,同时在编辑器(Editor)中生成设计图和各种开发语言的代码,支持 C++, Python, XRC, Lua 和 PHP 等。设计完成后,开发者只需要把对应页面的目标语言代码拷贝到自己的工程源文件中即可直接使用。
如需要安装 wxFormBuilder,建议前往官方下载页面获取对应平台安装包
https://github.com/wxFormBuilder/wxFormBuilder/releases
下面就使用 wxFormBuilder 来设计一个简单的界面。
一般界面都以 Frame 为底,所以在空白工程基础上添加一个 Frame 窗口。点击工作空间顶部的组件面板(Component Palette) -> Forms 类型 -> Frame 控件,如图

先在左边的控件树(Object Tree)面板里点击刚添加的 Frame 控件,然后在右边的对象属性(Object Properties)面板中就可以修改这个控件的属性了。这里修改控件的属性 name 为 w_frame_xrc,title 为 hello Python GUI APP!。最终输出代码会以一个窗口类的形式输出,而最底层窗口的 name 属性会决定这个窗口类的类名。

打算设计一个简单的名单录,为演示简单起见,只添加人员名称,而不编辑或者删除。按照元素控件的层次逐个添加,设计图最终效果图如下

如需要获取本工程所有源文件,包括设计文件等,可查看文末的链接。
设计界面完成后,点击编辑器(Editor)中的 Python 标签,复制窗口内所有内容,粘贴保存到新建的源文件 w_frame_xrc.py 中。
// w_frame_xrc.py
# -*- coding: utf-8 -*-
###########################################################################
## Python code generated with wxFormBuilder (version Oct 26 2018)
## http://www.wxformbuilder.org/
##
## PLEASE DO *NOT* EDIT THIS FILE!
###########################################################################
import wx
import wx.xrc
###########################################################################
## Class w_frame_xrc
###########################################################################
class w_frame_xrc ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"hello Python GUI APP!", pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
bSizer1 = wx.BoxSizer( wx.VERTICAL )
bSizer2 = wx.BoxSizer( wx.VERTICAL )
sbSizer1 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"输入信息" ), wx.VERTICAL )
bSizer3 = wx.BoxSizer( wx.HORIZONTAL )
self.m_staticText_name = wx.StaticText( sbSizer1.GetStaticBox(), wx.ID_ANY, u"名字:", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText_name.Wrap( -1 )
bSizer3.Add( self.m_staticText_name, 0, wx.ALL, 5 )
self.m_textCtrl_name = wx.TextCtrl( sbSizer1.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer3.Add( self.m_textCtrl_name, 1, wx.ALL, 5 )
sbSizer1.Add( bSizer3, 1, wx.EXPAND, 5 )
bSizer2.Add( sbSizer1, 1, wx.EXPAND, 5 )
bSizer4 = wx.BoxSizer( wx.VERTICAL )
bSizer4.Add( ( 0, 0), 1, wx.EXPAND, 5 )
self.m_button_add = wx.Button( self, wx.ID_ANY, u"添加", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer4.Add( self.m_button_add, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )
bSizer4.Add( ( 0, 0), 1, wx.EXPAND, 5 )
bSizer2.Add( bSizer4, 1, wx.EXPAND, 5 )
bSizer1.Add( bSizer2, 0, wx.EXPAND, 5 )
self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, u"列表:", wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_staticText2.Wrap( -1 )
bSizer1.Add( self.m_staticText2, 0, wx.ALL, 5 )
bSizer5 = wx.BoxSizer( wx.VERTICAL )
self.m_listCtrl_info = wx.ListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT )
bSizer5.Add( self.m_listCtrl_info, 1, wx.ALL|wx.EXPAND, 5 )
bSizer1.Add( bSizer5, 1, wx.EXPAND, 5 )
self.SetSizer( bSizer1 )
self.Layout()
self.Centre( wx.BOTH )
def __del__( self ):
pass
为了使用 wxFormBuilder 构建器生成的代码,可以简单修改前面的代码,如下
// main.py
import wx
import w_frame_xrc
app = wx.App()
window = w_frame_xrc.w_frame_xrc(parent=None)
window.Show()
app.MainLoop()
执行程序
python main.py

现在界面看起来有点内容了!
实现逻辑
前面的界面还不具备任何的实际功能,为了让其执行设计的功能,添加额外的逻辑在所难免。
比如,点击按钮 添加,就触发动作把输入框中的名字添加到下方的列表中,并清空输入框,因此需要在原来的窗口类 w_frame_xrc.w_frame_xrc 基础上添加一些逻辑功能代码。
不过,wxFormBuilder 构建出来的代码一般不建议直接修改,所以先对原来的窗口类 w_frame_xrc.w_frame_xrc 进行派生,再在派生类中补充逻辑功能代码。派生类的代码存在单独的源文件 w_frame.py 中。
// w_frame.py
import wx
import w_frame_xrc
class w_frame(w_frame_xrc.w_frame_xrc):
def __init__(self, parent):
super(w_frame, self).__init__(parent)
self.m_listCtrl_info.ClearAll()
self.m_listCtrl_info.InsertColumn(0, u'名字', width=140)
self.m_button_add.Bind(wx.EVT_BUTTON, self.on_button_add)
def on_button_add(self, event):
value = self.m_textCtrl_name.GetValue()
if not value:
print("You didn't enter anything!")
else:
self.m_listCtrl_info.InsertItem(self.m_listCtrl_info.GetItemCount(), value)
self.m_textCtrl_name.Clear()
可以看到,当控件有特定的事件需要绑定连接到处理句柄时,可以通过 Bind() 方法,传入 EVT_xxx 事件类型和处理句柄(可调用对象,比如,函数等)。如果需要将已绑定的某个事件断开连接,可以将处理句柄位置参数设为 None 即可。
然后,main.py 也需要稍作修改,如下
// main.py
import wx
import w_frame
app = wx.App()
window = w_frame.w_frame(parent=None)
window.Show()
app.MainLoop()
好了,现在再来测试一下刚添加的逻辑,程序启动后往里添加几个名单看看吧。
python main.py

部署发布
目前来看,工程里都是一些以源码文件形式存在的脚本,但是在最终用户使用时,都是习惯于直接双击一个 exe 文件来启动软件进程。
下面就介绍一种对 Python 脚本工程打包的工具,目标是最终输出一个可执行的 exe 文件。
这个工具就是 pyinstaller,使用之前需要确认一下自己的环境里是否已经安装有这个第三方包,还用指令 pip list 即可查看。
如果确认过没有,那么用下面的指令可以安装
pip install pyinstaller
假设已经安装完毕,直接打包。选项 -F 后边输入启动脚本文件
pyinstaller -F main.py
启动打包过程之后,工程目录下面会自动生成一个新目录 dist 用于存放输出的目标文件。由于上面的打包指令没有指明输出的目标文件名,所以默认输出为脚本文件同名,如 main.exe。
如果需要指明输出的目标文件名,可以加上选项 -n。比如要输出目标为 demo.exe,可以这样
pyinstaller -F main.py -n demo
也许有的同学喜欢让打包的输出文件带上图标,那么可以加上选项 -i。比如工程目录里有一份图标文件 logo.ico,需要让打包后输出文件带上这个图标,可以这样
pyinstaller -F main.py -n demo -i logo.ico
打包完毕,双击程序 demo.exe,可能会发现在运行起来的软件背景里,老是有个命令行的窗口,这样子真的很碍眼!
怎样把终端窗口给隐藏掉呢?打包的时候带上选项 -w,这样
pyinstaller -F main.py -n demo -i logo.ico -w
网上有些同学喜欢吐槽 pyinstaller 打包出来的目标文件体积过大,关于这个问题的解决思路是,工程开发(包括目标文件打包输出)应该在配置好的单独虚拟环境下进行,环境中不应该安装任何不需要的第三方包!
全文到这里算是结束了,欢迎你的留言!
工程代码仓库:git@github.com:ifi-leung/python_gui_wx.git
题外话
当下这个大数据时代不掌握一门编程语言怎么跟的上脚本呢?当下最火的编程语言Python前景一片光明!如果你也想跟上时代提升自己那么请看一下.

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。

👉优快云大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。


二、Python必备开发工具
工具都帮大家整理好了,安装就可直接上手!
三、最新Python学习笔记
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

四、Python视频合集
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

五、实战案例
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

六、面试宝典


简历模板
👉
优快云大礼包:gift::[全网最全《Python学习资料》免费赠送:free:!](https://blog.youkuaiyun.com/weixin_68789096/article/details/132275547?spm=1001.2014.3001.5502)
(安全链接,放心点击)
若有侵权,请联系删除
217

被折叠的 条评论
为什么被折叠?



