wxPython¶
虽然已经有一个完整的桌面通讯录了。可是小白并不满足,因为界面太丑了,而且也没有通常的软件那些的菜单栏,工具栏等。 他希望在MM面前秀一番,不想再被鄙视了。于是,小白决定,在给MM用之前,先改进一下自己基本 Tkinter 的通讯录。 小白还记得 Python 邮件列表的朋友推荐过 wxPython。wxPython 对 Windows 有更好地支持,而且提供更多的类库。于是,他决定,用 wxPython 重新写 通讯录。 虽然从来没有用过 wxPython ,但是已经有了用 Tkinter 做软件的经验,小白对学习过程还是有一个比较粗略的思路,首先,应该先安装 wxPython , 然后,找一个简单的可以运行的 wxPython 程序,接着了解 wxPython 的基本元素 (widget),学习布局管理器 (layout manager) 把这些 基本元素合理摆放,最后通过事件处理,使菜单,工具栏和搜索等按钮调用以前写好的添加、搜索、删除等通讯录操作函数。
小白挽起袖子,开工!先安装 wxPython 吧。
一、wxPython 程序库安装¶
小白用 “wxPython download” 在必应上搜索了一下,在搜索结果的第一项中,直接找到了 wxPython 的下载页面,如下:
http://www.wxpython.org/download-2.6.4.0.php
wxPython 的下载页面很详细地介绍了各个操作系统,如 Windows, Linux/Unix ,和 Mac 的安装方法。小白暗想,那些用 linux/unix 操作系统的 都是天才,他们根本不用看,肯定会安装。比如 Ubuntu下,打开 console, 输入 “sudo apt-get install wxPython” 就可以了。小白 虽然还是个菜鸟,可是幸好 Windows 下安装并不难。只需要下载正确的版本,用鼠标点击就可以完成安装了。小白用的是 Windows XP 操作系统, 使用的 Python 2.5。于是小白下载了 Python 2.5 的 win32-Unicode。下载完后,小白点击那个.exe文件,一路点击”下一步”完成了 wxPython 程序库的安装。 在 wxPython 的下载页面,小白发现,wxPython 还提供一些文档和示例。小白把 win32-docs-demos 也下载下来,安装了。不过,用 wxPython 编写界面,并不需要安装 win32-docs-demos。 小白打开 Python IDLE ,输入下面的代码,尝试导入 wxPython 的库函数。
>>import wx
恩!没有报错。说明用 Python +wxPython 的开发环境已经配置好了。
二、入门程序,Hello, wxPython¶
和 Tkinter 的学习过程一样,小白决定先从网上找一个 wxPython 的最简单程序。 “用 wxPython 写个 Hello, wxPython 程序吧。” 小白发现 wxPython 的官方网站(www.wxpython.org)有一个 wiki ,提供了很多简单的示例程序和丰富的文档。小白参照最简单的入门 程序,略做修改,写出了一个 Hello, wxPython 的程序。参见源代码 wx/HellowxPython.py 。
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 14:35:08 2011
@author: He Jibo
hejibo@ueseo.org
University of Illinois
a simple demo program for wxPython: "Hello, wxPython "
"""
import wx
class Application(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Hello wxPython", size = (300, 200))
panel = wx.Panel(self)
# 文本标签
txt = wx.StaticText(panel, -1, "Hello wxPython!")
#布局管理器,使用BoxSizer布局
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.TOP|wx.LEFT, 20)
panel.SetSizer(sizer)
self.Centre()
self.Show(True)
if __name__ == "__main__":
app = wx.App(0)
Application(None)
app.MainLoop()
在上面的示例中, 小白先建立了一个面板 (wx.Panel) 和一个文本标签(wx.StaticText),然后用布局管理器 (wx.BoxSizer) 将文本标签放到面板上。 点 F5 看看什么样吧。
三、GUI 图纸施工¶
wxPython 的基本元素(widget)¶
- 小白希望给软件增加菜单栏和工具栏。于是他打算先了解 wxPython 的基本元素。于是,他分别搜索了 “wxPython menu bar” 和
- “wxPython tool bar” 。通过搜索,他了解到,wxPython 主要有以下这些基本元素:
Button ,ToggleButton ,BitmapButton ,StaticLine ,StaticText ,StaticBox ,ComboBox ,CheckBox ,StatusBar ,MenuBar, RadioButton ,Gauge ,Slider ,ListBox ,SearchCtrl, SpinCtrl ,SplitterWindow ,ScrolledWindow , Notebook ,Panel 等。
根据新的需求,和 Tkinter 编程的经验,小白记下了他可能用用到的一些元素,如 Button, StaticText, ListBox, MenuBar,StatusBar 和 SearchCtrl。了解了wxPython的基本元素后,小白打算尝试写一个具有菜单栏和工具栏的简单界面。小白修改了HellowxPython.py, 得到了一个新的具有菜单和工具栏的界面。参见源代码 wx/MenuToolBar.py 。
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 15:11:08 2011
@author: He Jibo
hejibo@ueseo.org
University of Illinois
demo how to use toolbar and menubar in wxpython
"""
import wx
class SimpleMenu(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Hello wxPython", size = (300, 200))
panel = wx.Panel(self)
#创建菜单栏
menu_bar = wx.MenuBar()
files = wx.Menu()
files.Append(101, "&Quit", "Quit application")
menu_bar.Append(files, "&File")
self.SetMenuBar(menu_bar)
#创建工具栏
self.tool_bar = self.CreateToolBar()
self.tool_bar.AddLabelTool(201, "exit", wx.Bitmap("icons/exit.png"))
self.tool_bar.Realize()
# 文本标签
txt = wx.StaticText(panel, -1, "Hello wxPython!")
#布局管理器,使用BoxSizer布局
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.TOP|wx.LEFT, 20)
panel.SetSizer(sizer)
self.Centre()
self.Show(True)
if __name__ == "__main__":
app = wx.App(0)
SimpleMenu(None)
app.MainLoop()
小白点击 F5 运行 MenuToolBar.py ,发现这个界面包含菜单和工具栏,分别有一个退出功能(仅仅有样子,还不能真正地退出,需要 学习事件绑定后,才能完成退出动作)。主界面上有一个 StaticText 显示 “Hello wxPython”.
随后,小白根据学习 Tkinter 的经验,学习了 Button, SearchCtrl, ListBox 这三个元素的使用方法。Button, SearchCtrl, ListBox 分别用于 搜索按钮,搜索输入框和呈现搜索结果。
wxPython 的布局管理(Layout manager)¶
了解了需要用到的元素后,接着,小白需要合理地摆放这些元素。虽然他还不知道 wxPython 如何布局,不过小白已经使用过 Tkinter 的 布局管理器。根据以往经验,小白搜索了 “wxPython layout” 和 “wxPython 布局管理” 。经过搜索以及阅读相关结果,小白发现, wxPython主要有 BoxSizer ,StaticBoxSizer ,GridSizer ,FlexGridSizer ,GridBagSizer 这几种布局管理方式。
BoxSizer 将元素以行或者列地方式摆放。一个 BoxSizer 还可以包含另一个 BoxSizer 。BoxSizer 的使用方式如下:
box = wx.BoxSizer(integer orient)
首先定义一个 BoxSizer,然后将元素加入到 BoxSizer 里。放置参数 integer orient 可以是 wx.VERTICAL 或者 wx.HORIZONTAL,分别以纵向或者横向的方式放置元素。
GridSizer 以二维坐标的方式放置元素。每个坐标的元素具有相同的大小。 GridSizer 的使用方式如下:
grid = wx.GridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)
FlexGridSizer,与 GridSizer 类似,也是使用二维坐标的方式放置元素。不过,FlexGridSizer 比 GridSizer更自由。同一行内的元素都具有相同的高度,同一列中的元素具有相同的宽度。但是不同行或者之间的元素的高度或者宽度,可以不相同。FlexGridSizer 的使用方式如下:
wx.FlexGridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)
GridBagSizer 是 wxPython 中最为复杂的布局管理方式。GridBagSizer可以更直接地指定元素的具体位置。每一个元素都可以覆盖多行或者多列。GridBagSizer 的使用方式如下:
wx.GridBagSizer(integer vgap, integer hgap)
小白的通讯录只有三个元素,比较简单,BoxSizer 就可以满足需求。小白分别用 SearchCtrl,Button 和 ListBox 分别创建了搜索条,搜索按钮和搜索结果框。然后用一个横向的 BoxSizer 将搜索条和搜索按钮结合在一起,再用一个纵向的 BoxSizer加入搜索结果框。代码如下 (或参见源代码wx/wxLayoutManager.py。)
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 14:35:08 2011
@author: He Jibo
hejibo@ueseo.org
University of Illinois
a simple demo program for wxPython: "Hello, wxPython "
"""
import wx
class LayoutDemo(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.search_box = wx.SearchCtrl(self, size = (400,-1), style = wx.TE_PROCESS_ENTER)
self.search_box.ShowCancelButton
self.search_button = wx.Button(self, -1, "Search!")
self.output_list = []
self.results = wx.ListBox(self, 26, (-1, -1), (170, 130),
self.output_list, wx.LB_SINGLE)
self.__do_layout()
def __do_layout(self):
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(self.search_box, wx.ALL, 8)
hbox.Add(self.search_button, flag = wx.RIGHT, border = 8)
vbox.Add(hbox, flag = wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border = 10)
vbox.Add(self.results, 1, wx.EXPAND, 0)
self.SetSizer(vbox)
vbox.Fit(self)
self.Layout()
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame = LayoutDemo(None, -1, "Layout Manager demo")
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
小白点击F5运行wxLayoutManager.py,并将截图放置如下。图中已经具备通讯录的主要界面了。
wxPython 的事件处理(Event and Bindings)¶
wxLayoutManager.py 中已经具备主要界面了。还差的就是把事件处理函数(包括搜索、增加和删除联系人)与按钮绑定。虽然从来没有用过 wxPython ,但是根据 Tkinter 的经验,小白认为,这应该不难,不就是将按钮绑定(Bind)到函数吗?搜!小白搜索了一下 “wxPython event binding”,以查询 wxPython 中事件绑定的具体方法。原来,self.Bind(wx.EVT_BUTTON,self.search,self.search_button)这样就可以了呀?其中 self.search 是事件处理函数,self.search_button 是被绑定的搜索按钮。wx.EVT_BUTTON 为按钮点击事件。
小白决定动手试一试。于是在 wxLayoutManager.py 的基础上增加一个搜索功能。在文本文件中搜索联系人,小白以前在基于 CLI 和 Tkinter 的通讯录中已经使用多次了。唯一需要查询的是,在得到搜索结果后,如何更新 ListBox 这个搜索结果框的值 。经过查询,小白发现Clear()函数可以重置 ListBox 的值,Append(”“)函数可以在 ListBox 中增加新值。小白在 wxLayoutManager.py 在基础上,增加了搜索联系人的功能。代码如下 (或参见源代码wx/wxEventBinding.py。)
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 21:22:16 2011
@author: He Jibo
hejibo@ueseo.org
University of Illinois
a demo program showing event binding in wxpython
"""
import wx
FILENAME="addressfile.txt"
class LayoutDemo(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.search_box = wx.SearchCtrl(self, size=(400,-1), style=wx.TE_PROCESS_ENTER)
self.search_box.ShowCancelButton
self.search_button = wx.Button(self, -1, "Search!")
#事件绑定
self.Bind(wx.EVT_BUTTON,self.search,self.search_button)
self.output_list=[]
self.results = wx.ListBox(self, 26, (-1, -1), (170, 130),
self.output_list, wx.LB_SINGLE)
self.__do_layout()
def __do_layout(self):
'''布局管理'''
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(self.search_box, wx.ALL, 8)
hbox.Add(self.search_button, flag=wx.RIGHT, border=8)
vbox.Add(hbox, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)
vbox.Add(self.results, 1, wx.EXPAND, 0)
self.SetSizer(vbox)
vbox.Fit(self)
self.Layout()
def search(self,evt):
'''
查找联系人函数
'''
self.results.Clear()
query = self.search_box.GetValue().strip()
try:
x = open(FILENAME,"r")
isfound = False
for y in x:
if query in y:
self.results.Append(y)
isfound=True
if isfound==False:
self.results.Append("not found")
x.close()
except:
self.results.Append("data file does not exist")
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame = LayoutDemo(None, -1, "Event binding demo")
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
完善通讯录¶
至此,小白已经学会了 wxPython 中的基本元素,布局管理和事件响应的方法了。 在 MenuToolBar.py,小白学会了如何创建目录和工具条。仿照 File(文件)目录下退出功能的写法,小白进一步完成了 Edit(编辑)目录下的添加和删除功能,以及 Help(帮助)目录。
在 Tkinter 一节,小白学会了如何使用 Tkinter 创建新建联系人界面的方法。新建联系人界面对于 wxPython 来说,主要是 StaticText 和 TextCtrl 两个元素,分别用于文字标签和联系人信息输入。然后用 BoxSizer 将这些元素摆放在一起。仿照以上讲的 wxPython 的基本元素和布局管理方式,小白成功地用 wxPython 重写了新建联系人界面。
最后,小白找回了他在写 CLI 通讯录时的删除联系人和保存联系人的函数,通过 wxPython 的事件处理方法,实现了联系人删除、保存操作。小白在使用 Tkinter 时,也实现过保存联系人的函数。本节也详细讲了如何实现联系人搜索操作。将这些曾经写过的函数贴在一起,略做修改。小白很容易的完成了基于 wxPython 的通讯录。他把代码放在了 wx/wxAddressbookzh.py。您也去看看他写的代码吧?