Python:界面开发,wx入门篇

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/3Yb_YAKiMte_f5HanetXiA


本文大概 3617 个字,阅读需花 10 分钟
内容不多,但也花了一些精力
如要交流,欢迎评论区留言
谢谢你的点赞收藏分享

如果你接触过桌面 GUI 软件开发,那么你一定会对 MFC、WPF、Qt 等有或多或少的了解。

那么什么是 GUI 软件呢?GUI 软件是带有用户交互界面的软件,有按钮,有窗口,还有很多其它用于和用户交互的小部件。常见的例子是,比如各个厂家推出的浏览器,上面有标签,有按钮,有网址输入栏,有网页内容展示窗口和状态栏等等。

不过,这里打算为大家介绍一下使用 Python 怎么去做 GUI 开发,因为会 Python 的人更多,相信其中也有很多人对开发界面软件颇有兴趣。好,看下文。

Python 的 GUI 开发框架有好几种,比较主流的有 wxPython、PyQt、Tkinter,这三种都是跨平台方案。

wxPython 是 Python 的第三方库,代码实现基于 C++ 的 wxWidgets 库封装,呈现的界面风格和系统本地风格一致。其许可协议规定如果直接引用 wxPython 的二进制库文件,则可以随便使用。

可以看看在 linux 系统下基于 wxPython 的界面程序是什么样子

wx.png

PyQt 是 Qt 平台的 Python 版本,自绘的界面风格,许可协议对商业应用不太友好。虽然和原生风格已经很接近了,但还是有人能挑出刺来。

可以看看在 linux 系统下基于 PyQt 的界面程序是什么样子

pyqt.png

Tkinter 是 Python 自带的 GUI 开发框架,也是本地化风格。

看看在 linux 系统下基于 Tkinter 的界面,风格真的有些过时了

tk.png

值得一提的是,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

1.JPG

通过显示的程序窗口,可看到整个程序只做了很基本的 UI 窗口显示,但是代码结构也相对简单。如果需要实现更复杂的窗口界面呢?往下看。

使用 wxFormBuilder 设计复杂 UI

从上一节的 demo 代码中,看到要实现显示一个窗口是很简单的,但是如果窗口内部包含了很多其它部件呢?

继续手堆代码吗?不是不可以,但是这样子的做法对工程或者项目后期维护是很不利的。

那么怎么办?很巧,wxWidgets 框架提供了一个 GUI 构建器 wxFormBuilder。

2.JPG

打开 wxFormBuilder,可以看到提供了非常多的工具,而且自动创建一个空白工程。为了设计需要的界面,开发者可以通过手动点击组件面板(Component Palette)中不同种类的各种控件来布局界面,同时在编辑器(Editor)中生成设计图和各种开发语言的代码,支持 C++, Python, XRC, Lua 和 PHP 等。设计完成后,开发者只需要把对应页面的目标语言代码拷贝到自己的工程源文件中即可直接使用。

如需要安装 wxFormBuilder,建议前往官方下载页面获取对应平台安装包

https://github.com/wxFormBuilder/wxFormBuilder/releases

下面就使用 wxFormBuilder 来设计一个简单的界面。

一般界面都以 Frame 为底,所以在空白工程基础上添加一个 Frame 窗口。点击工作空间顶部的组件面板(Component Palette) -> Forms 类型 -> Frame 控件,如图

4.jpg

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

5.JPG

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

6.JPG

如需要获取本工程所有源文件,包括设计文件等,可查看文末的链接。

设计界面完成后,点击编辑器(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

9.JPG

现在界面看起来有点内容了!

实现逻辑

前面的界面还不具备任何的实际功能,为了让其执行设计的功能,添加额外的逻辑在所难免。

比如,点击按钮 添加,就触发动作把输入框中的名字添加到下方的列表中,并清空输入框,因此需要在原来的窗口类 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

10.JPG

部署发布

目前来看,工程里都是一些以源码文件形式存在的脚本,但是在最终用户使用时,都是习惯于直接双击一个 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

posted @ 2022-12-15 03:22  ENG八戒  阅读(4870)  评论(1编辑  收藏  举报