Python 的出现可以帮助我们快速解决实际的问题,提高工作效率。如果给 Python 脚本加上一个 GUI 的话,不仅可以进一步提升使用效率(不用每次停止运行去修改参数),而且还能把自己程序分享给不懂编程的朋友们使用,的确让人激动不已!
wxPython
是基于 Python 的跨平台 GUI 扩展库,是对 wxWidgets( C++ 编写)封装实现,也是目前最为流行的 GUI 库之一。
本文我们就来和大家一起学习 wxPython
的使用方法,运用知识点打造股票行情分析界面,为实现自己的桌面交互界面添砖加瓦。
本文主要内容包括:
- 通过最小框架的实现快速入门
wxPython
- 界面的布局管理及多页面之间的切换,嵌入
Matplotlib
可以显示各种图形 - 不仅介绍基本的按钮、文本、工具栏、菜单栏的使用,还有高级的树形列表、Excel 表格、进度条、滑块、日历、对话框的扩展介绍
- 结合以上知识点,制作一个基础版的股票行情分析界面 ,可以交互查看个股走势
第三方库 wxPython
简介
目前,跨平台的 GUI 工具库较为有名的有 Tk
、GTK
、Qt
和 wxWidgets
,尽管它们之间各有利弊,但很大程度上仍然取决于个人的喜好。
本文推荐使用 wxWidgets
,主要原因一方面是因为 wxWidgets
受众群体较大,另一方面 wxWidgets
是标准 C++实现的,不仅上手快,而且在不同平台上与各类工具的兼容较好,能够做到完全的原生界面(Native GUI),是真正的跨平台工具库。
在 Python 环境下 wxPythons
对 wxWidgets 实现了封装,形成了基于 Python 的跨平台 GUI 工具库。 在使用之前我们需要导入 wxPython
工具库,如下所示:
import wx
本文涉及到的日历控件 DatePickerCtrl
已经迁徙至 wx.adv 模块中,需要导入模块,如下所示:
import wx.adv
本文涉及到电子表格控件 Grid,需要导入模块,如下所示:
import wx.grid
本文涉及到树形列表控件 TreeListCtrl,需要导入模块,如下所示:
import wx.gizmos
最小 GUI 框架的实现
在 wxPython
中,GUI 程序由 wx.App、wx.Frame、wx.Panel 以及其他 widget 控件(如 wx.ComboBox
, wx.Button
)组成的。我们简单地梳理下这些组件的作用以及之间的联系。
一个 GUI 程序只有一个 wx.App
创建的实例用于执行事件循环,至少一个 wx.Frame 创建的实例(Frame)作为其他控件的容器,在 Frame 中至少有一个wx.Panel
的实例(Panel)用来控制整个 Frame 的布局,而其他的控件则建立在 Panel 之上。
接下来我们逐步来实现最小 GUI 框架程序。其实启动一个最基础的 GUI 程序只需要一个 wx.App 实例、一个 wx.Frame
实例、一个默认生成的 wx.Panel
实例即可,此处例程再额外添加一个 wx.Button
的实例。
代码如下所示:
app = wx.App()#创建应用程序
frame = wx.Frame(None, -1, "Test Frame")
btn = wx.Button(frame, -1, label="Open")#在 frame 上实例化 wx.Button
frame.Show(True)#在调用 app.MainLoop 前显示 frame
app.MainLoop()#进入主事件循环
在实际应用中,我们更倾向于创建自定义的 Frame 类、Panel 类,这样可以更灵活地设计我们所需要的 GUI 界面。 接下来,我们给出真正意义上的最小 GUI 框架程序,任何一个 GUI 程序的开发都可以在这个框架基础之上展开。 完整代码如下所示:
class Panel(wx.Panel):#继承 wx.Panel
def __init__(self, parent):#构造函数
wx.Panel.__init__(self, parent=parent, id=-1)
#此处添加 Panel 代码
class Frame(wx.Frame):#继承 wx.Frame
def __init__(self):#构造函数
wx.Frame.__init__(self, parent=None, title='Test Frame')
self.DispPanel = Panel(self)
# 此处添加各类控件
btn = wx.Button(self.DispPanel, -1, label="Open")
class App(wx.App):#继承 wx.App
def OnInit(self):
self.frame = Frame()
self.frame.Show()
self.SetTopWindow(self.frame)#设置当前 Frame 为应用程序的顶级窗口
return True
if __name__ == '__main__':
app = App()
app.MainLoop()
此处在 Frame 类和 Panel 类中定义了init()方法,可以更灵活地添加自定义的界面风格。
App 类中定义了 OnInit()
方法。该方法在应用程序创建后到事件循环开始前被 wx.App
父类调用,需要返回一个为 True 的布尔值。
另外,SetTopWindow()
方法用于设置当前 Frame 为应用程序的顶级窗口。 应用程序一旦进入主事件循环,控制权将转交给 wxPython
,程序会响应用户的鼠标和键盘事件。当应用程序的所有 Frame 关闭后 app.MainLoop()
方法结束并退出程序。
最小 GUI 框架程序运行效果如图所示:
Sizer 布局管理的介绍
GUI 程序的开发中,界面布局是很重要的一个环节,合理的界面布局是能够给予用户良好使用体验的。
绝对坐标的定位方式需要为每一个控件设计大小和位置,修改布局时十分繁琐,而且调整容器尺寸时控件无法对应改变。
幸运的是 wxPython 的布局管理器 Sizer 能对容器中的控件进行更优雅的布局管理,随着容器尺寸的变化自动计算控件最优化的大小和位置。
因此关于界面布局,更方便的是用 Sizer。
在 wxPython
中定义的 Sizer 主要有 wx.BoxSizer
,wx.StaticBoxSizer
,wx.GridSizer
,wx.FlexGridSizer
和 wx.GridBagSizer
这几种,它们都继承于 Wx.Sizer
类,并在此之上各自具有管理窗口布局的规则。
接下来我们以雏形的量化交易系统 GUI 界面为例,来介绍下如何运用 wxPython
的 Sizer 布局管理功能。
wx.BoxSizer 的布局方向分为横向和纵向两种,并且可以在横向或纵向方向上嵌套包含子 Sizer 的布局。此处如目标效果图所示,将 GUI 界面整体布局嵌套为三层:
第一层布局。ParaPanel(参数面板)、DispPanel(显示面板)、CtrlPanel(控件面板)采用 wx.Boxsizer 的横向布局作为第一层布局。
第二层布局。在 ParaPanel 中,将多个 wx.StaticBoxSizer 布局管理器采用 wx.Boxsizer 的纵向排布,作为第二层嵌套布局。同为第二层嵌套布局的还有 CtrlPanel 中,wx.FlexGridSizer 布局管理器和 wx.TextCtrl 控件采用 wx.Boxsizer 的纵向排布。
- 第三层布局。在 wx.StaticBoxSizer 中将 wx.StaticText、wx.ComboBox 控件作为第三层嵌套排布。以及在 wx.FlexGridSizer 布局管理器指定的四个 wx.Button 控件的排布。
接下来,我们用代码来实现界面布局的目标效果。
1. 创建各个面板
首先在 GUI 框架程序的 Frame 类中分别创建 ParaPanel、DispPanel、CtrlPanel 对象,如下所示:
#创建显示区面板
self.DispPanel = Panel(self) # 自定义
#创建参数区面板
self.ParaPanel = wx.Panel(self,-1)
#创建控制面板
self.CtrlPanel = wx.Panel(self,-1)
排布这三个面板之前,需要先在这三个面板内以嵌套方式实现各自控件的布局。
2.ParaPanel 中嵌套布局
当布局 ParaPanel 中的控件时,需要先创建一个纵向 wx.BoxSizer 布局管理器的实例 vboxsizera 用于排列控件,如下所示:
vbox_sizer_a = wx.BoxSizer(wx.VERTICAL) # 纵向 box
然后在 ParaPanel 中分别创建 wx.StaticBox、wx.ComboBox 的实例,再创建一个 wx.StaticBoxSizer 布局管理器的实例 stockparasizer,stockparasizer 作为行情参数的控件布局。将控件作为参数传入至 stockparasizer 中进行嵌套的布局管理,如下所示:
# 行情参数
stock_para_box = wx.StaticBox(self.ParaPanel, -1, u'行情参数')
stock_para_sizer = wx.StaticBoxSizer(stock_para_box, wx.VERTICAL)
# 行情参数——股票名称
self.stock_name_list = ["浙大网新", "高鸿股份", "天威视讯", "北方导航"]
self.stock_name_cbox = wx.ComboBox(self.ParaPanel, -1, "浙大网新", choices = self.stock_name_list,
style = wx.CB_READONLY|wx.CB_DROPDOWN) #股票名称
self.stock_name_text = wx.StaticText(self.ParaPanel, -1, u'股票名称')
stock_para_sizer.Add(self.stock_name_text,proportion=0,flag=wx.EXPAND|wx.ALL,border=2)
stock_para_sizer.Add(self.stock_name_cbox, 0, wx.EXPAND|wx.ALL|wx.CENTER, 2)
同理,我们可以在 ParaPanel
中创建多个类似的布局控件实例,并与 stockparasizer
一同添加至 vboxsizera
布局管理器中。如下所示:
vbox_sizer_a = wx.BoxSizer(wx.VERTICAL) # 纵向 box
vbox_sizer_a.Add(stock_para_sizer,proportion=0,flag=wx.EXPAND|wx.BOTTOM,border=5)
vbox_sizer_a.Add(back_para_sizer,proportion=0,flag=wx.EXPAND|wx.BOTTOM,border=5)
vbox_sizer_a.Add(pick_para_sizer,proportion=0,flag=wx.EXPAND|wx.BOTTOM,border=5)
vbox_sizer_a.Add(download_para_sizer,proportion=0,flag=wx.EXPAND|wx.BOTTOM,border=5)
最后对 ParaPanel
调用 SetSizer()
方法使布局有效。如下所示:
self.ParaPanel.SetSizer(vbox_sizer_a)
3.CtrlPanel 中嵌套布局
对于 CtrlPanel
也是类似的步骤。
先创建一个纵向 wx.BoxSizer
布局管理器的实例 vboxsizera 用于排列控件,如下所示:
vbox_sizer_b = wx.BoxSizer(wx.VERTICAL) # 纵向 box
创建 wx.GridSizer 布局管理器的实例 self.FlexGridSizer,创建四个 wx.Button 控件的实例,并通过 Add()
方法添加至二维网格中。如下所示:
self.FlexGridSizer=wx.FlexGridSizer(rows=2, cols=2, vgap=3, hgap=3)
# 行情按钮
self.Firmoffer = wx.Button(self.CtrlPanel,-1,"实盘")
# 下载按钮
self.download = wx.Button(self.CtrlPanel,-1,"下载")
# 选股按钮
self.Stockpick = wx.Button(self.CtrlPanel,-1,"选股")
# 回测按钮
self.Backtrace = wx.Button(self.CtrlPanel,-1,"回测")
#加入 Sizer 中
self.FlexGridSizer.Add(self.Firmoffer,proportion = 1, border = 5,flag = wx.ALL | wx.EXPAND)
self.FlexGridSizer.Add(self.Stockpick,proportion = 1, border = 5,flag = wx.ALL | wx.EXPAND)
self.FlexGridSizer.Add(self.Backtrace,proportion = 1, border = 5,flag = wx.ALL | wx.EXPAND)
self.FlexGridSizer.SetFlexibleDirection(wx.BOTH)
在 CtrlPanel
中创建 wx.TextCtrl
的实例,并与 FlexGridSizer
一同添加至 vboxsizerb 布局管理器中。如下所示:
self.TextAInput = wx.TextCtrl(self.CtrlPanel, -1, "股票信息提示:", style = wx.TE_MULTILINE|wx.TE_READONLY)#多行|只读
vbox_sizer_b = wx.BoxSizer(wx.VERTICAL) # 纵向 box
vbox_sizer_b.Add(self.FlexGridSizer,proportion=0,flag=wx.EXPAND|wx.BOTTOM,border=2) #proportion 参数控制容器尺寸比例
vbox_sizer_b.Add(self.TextAInput, proportion=1, flag=wx.EXPAND | wx.ALL, border=2)
最后对 CtrlPanel
调用 SetSizer()
方法使布局有效。如下所示:
self.CtrlPanel.SetSizer(vbox_sizer_b)
4.整体布局生效
由于三个面板是横向布局的关系,需要创建一个横向 wx.BoxSizer
布局管理器的实例(self.HBoxPanel
)用于排布这三个面板。如下所示:
self.HBoxPanelSizer = wx.BoxSizer(wx.HORIZONTAL)
布局完成 ParaPanel
和 CtrlPanel
中的控件后,使用 Add()
方法将三个面板添加至 self. HBoxPanelSizer
中,完成横向 BoxSizer 的布局,使用 SetSizer()
方法使布局有效。如下所示:
self.HBoxPanelSizer = wx.BoxSizer(wx.HORIZONTAL)
self.HBoxPanelSizer.Add(self.ParaPanel,proportion = 1.5, border = 2,flag = wx.EXPAND|wx.ALL)
self.HBoxPanelSizer.Add(self.DispPanel,proportion = 8, border = 2,flag = wx.EXPAND|wx.ALL )
self.HBoxPanelSizer.Add(self.CtrlPanel,proportion = 1, border = 2,flag = wx.EXPAND|wx.ALL )
self.SetSizer(self.HBoxPanelSizer)#使布局有效
嵌入 Matplotlib 的方法
上文介绍了 ParaPanel
和 CtrlPanel
的布局,剩下的 DispPanel
(显示面板)主要用于显示 Matplotlib
绘制的图形,接下来我们介绍下在 wxPython
中嵌入 Matplotlib
的方法。
Matplotlib
中底层的绘图操作是由后端( backend )程序处理的,后端程序会针对所选择的不同输出方式在对应的界面显示图像或者以图像文件形式进行保存。
此处针对我们的需求,将 Matplotlib
的后端输出选择为 wxPython
,这样即可在 wxPython
的 GUI 界面中嵌入 Matplotlib
的图形显示了。
首先导入 FigureCanvasWxAgg
类,它代表了真正进行绘图的后端,可以把绘图的程序逻辑连接到后端的绘图程序,在屏幕上绘制出来。如下所示:
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
在 Panel 类中建立面向对象之间所属的关系。将创建的图表 Figure 实例(self.figure)添加至 FigureCanvas 中,在 self.figure 中的处理属于 Matplotlib 的范畴之中,我们可以在 self.figure 中创建子图。如下所示:
self.figure = Figure()
self.subplot = self.figure.add_subplot(1, 1, 1)
self.FigureCanvas = FigureCanvas(self, -1, self.figure)#figure 加到 FigureCanvas
创建纵向 wx.BoxSizer
布局管理器实例(self.TopBoxSizer
),用于管理 FigureCanvas
中的布局,这样后续可以继续嵌入其他的控件,比如标签、导航栏、按钮、文本框等组件。设置完成后通过 SetSizer
命令使布局生效。如下所示:
self.TopBoxSizer = wx.BoxSizer(wx.VERTICAL)
self.TopBoxSizer.Add(self.FigureCanvas,proportion = -1, border = 2,flag = wx.ALL | wx.EXPAND)
self.SetSizer(self.TopBoxSizer)
将 DispPanel
与 ParaPanel
、CtrlPanel
一同添加至横向 BoxSizer
布局管理器中,以实现嵌套布局。
最终的布局显示效果如图所示:
于是就可以使用 Matplotlib
库在显示面板上绘图了。