Python:界面开发,wx入门篇
原创以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「englyf」 https://mp.weixin.qq.com/s/3Yb_YAKiMte_f5HanetXi
本文大概 3617 个字,阅读需花 10 分钟
内容不多,但也花了一些精力
如要交流,欢迎评论区留言
如果你接触过桌面 GUI 软件开发,那么你一定会对 MFC、WPF、Qt 等有或多或少的了解。
那么什么是 GUI 软件呢?GUI 软件是带有用户交互界面的软件,有按钮,有窗口,还有很多其它用于和用户交互的小部件。常见的例子是,比如各个厂家推出的浏览器,上面有标签,有按钮,有网址输入栏,有网页内容展示窗口和状态栏等等。
不过,这里打算为大家介绍一下使用 Python 怎么去做 GUI 开发,因为会 Python 的人更多,相信其中也有很多人对开发界面软件颇有兴趣。好,看下文。
Python 的 GUI 开发框架有好几种,比较主流的有 wxPython、PyQt、Tkinter,这三种都是跨平台方案。
wxPython 是 Python 的第三方库,代码实现基于 C++ 的 wxWidgets 库封装,呈现的界面风格和系统本地风格一致。其许可协议规定如果直接引用 wxPython 的二进制库文件,则可以随便使用。
可以看看在 linux 系统下基于 wxPython 的界面程序是什么样子
<p align=center><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b6d85fb6d274355876c856c10fbc012~tplv-k3u1fbpfcp-watermark.image?" alt="wx.png" /></p>
PyQt 是 Qt 平台的 Python 版本,自绘的界面风格,许可协议对商业应用不太友好。虽然和原生风格已经很接近了,但还是有人能挑出刺来。
可以看看在 linux 系统下基于 PyQt 的界面程序是什么样子
<p align=center><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ef2c2a6220a44a9cbc3a4be7c6441e2e~tplv-k3u1fbpfcp-watermark.image?" alt="pyqt.png" /></p>
Tkinter 是 Python 自带的 GUI 开发框架,但也是自绘的界面风格。
可以看看在 linux 系统下基于 Tkinter 的界面程序是什么样子
<p align=center><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0c14ae360fbd4568afd4e457dab47371~tplv-k3u1fbpfcp-watermark.image?" alt="tk.png" /></p>
值得一提的是,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
<p align=center><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0d8d81c219004c9690bf94bfbf809d4c~tplv-k3u1fbpfcp-watermark.image?" alt="1.JPG" /></p>
通过显示的程序窗口,可看到整个程序只做了很基本的 UI 窗口显示,但是代码结构也相对简单。如果需要实现更复杂的窗口界面呢?往下看。
使用 wxFormBuilder 设计复杂 UI
从上一节的 demo 代码中,看到要实现显示一个窗口是很简单的,但是如果窗口内部包含了很多其它部件呢?
继续手堆代码吗?不是不可以,但是这样子的做法对工程或者项目后期维护是很不利的。
那么怎么办?很巧,wxWidgets 框架提供了一个 GUI 构建器 wxFormBuilder。
<p align=center><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45ba510bc76e4947b16e9d176d224e61~tplv-k3u1fbpfcp-watermark.image?" alt="2.JPG" /></p>
打开 wxFormBuilder,可以看到提供了非常多的工具,而且自动创建一个空白工程。为了设计需要的界面,开发者可以通过手动点击组件面板(Component Palette)中不同种类的各种控件来布局界面,同时在编辑器(Editor)中生成设计图和各种开发语言的代码,支持 C++, Python, XRC, Lua 和 PHP 等。设计完成后,开发者只需要把对应页面的目标语言代码拷贝到自己的工程源文件中即可直接使用。
如需要安装 wxFormBuilder,建议前往官方下载页面获取对应平台安装包
https://github.com/wxFormBuilder/wxFormBuilder/releases
下面就使用 wxFormBuilder 来设计一个简单的界面。
一般界面都以 Frame 为底,所以在空白工程基础上添加一个 Frame 窗口。点击工作空间顶部的组件面板(Component Palette) -> Forms 类型 -> Frame 控件,如图
<p align=center><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/76b05a9f55cc44278e13fbad415a412c~tplv-k3u1fbpfcp-watermark.image?" alt="4.jpg" /></p>
先在左边的控件树(Object Tree)面板里点击刚添加的 Frame 控件,然后在右边的对象属性(Object Properties)面板中就可以修改这个控件的属性了。这里修改控件的属性 name 为
w_frame_xrc
,title 为
hello Python GUI APP!
。最终输出代码会以一个窗口类的形式输出,而最底层窗口的 name 属性会决定这个窗口类的类名。
<p align=center><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/efb1e013a6c1497ba5940d6f268c2876~tplv-k3u1fbpfcp-watermark.image?" alt="5.JPG" /></p>
打算设计一个简单的名单录,为演示简单起见,只添加人员名称,而不编辑或者删除。按照元素控件的层次逐个添加,设计图最终效果图如下
<p align=center><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b1623fd8a83742dc83b42c308c9429c7~tplv-k3u1fbpfcp-watermark.image?" alt="6.JPG" /></p>
如需要获取本工程所有源文件,包括设计文件等,可查看文末的链接。
设计界面完成后,点击编辑器(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
<p align=center><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cdf37790256744ee91084307314e3258~tplv-k3u1fbpfcp-watermark.image?" alt="9.JPG" /></p>
现在界面看起来有点内容了!
实现逻辑
前面的界面还不具备任何的实际功能,为了让其执行设计的功能,添加额外的逻辑在所难免。
比如,点击按钮
添加
,就触发动作把输入框中的名字添加到下方的列表中,并清空输入框,因此需要在原来的窗口类
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