1
另一种可供Python使用的GUI工具包叫做wxPython。目前这个工具对于Python环境来说还是陌生的,但正在Python开发者中间快速地流行起来。
wxPython是Python扩展模块,它封装了wxWindows C++类库。
wxPython是一个为Python提供的交叉平台GUI框架工具,它在Windows平台上相当成熟。它是基于流行的wxWindows C++框架的Python,为GUI
开发者提供了一种有吸引力的替代工具。
wxWindows
wxWindows是一个自由C++编程框架,被设计用来实现跨平台编程。wxWindows 2.0支持Windows 3.1/95/98/NT,Unix下支持
GTK/Motif/Lesstif,还有正在开发中的Mac版本。其它系统正在考虑中。
wxWindows是一套库函数,允许C++应用程序只需要微量的源代码改动,就可在几种不同类型的机构上编译和运行。每一种支持的GUI都有一
个对应的库( 象Motif或Windows)。同时为了实现GUI功能提供了通用的API,还为了处理一些通常要用到的操作系统设备提供了功能,应用程序
可以根据需要使用或替换,这样就会节省大量的编码工作。基本数据结构,象字符串,链表,和哈希表也提供了。
控件的本地版本,通用对话框,和其它的窗口类型被用在支持它们的平台上。对于其它的平台,相适应的替代品使用wxWindows自身来生成
。例如,在W in32平台上,使用了本地的列表控件,但是在GTK,具有相似功能的通用列表控件则是使用wxWindows类库创建的。
有经验的Windows程序员对于wxWindows对象模型会感到象是在家一样。类和原则的许多地方都很相似。例如,多文档界面,用GDI对象,如
刷子,笔,在上下文设置上绘图,等等。
wxWdinws + Python = wxPython
wxPython是一个Python扩展模块,它提供了一套从wxWindows库到Python语言的绑定。换句话说,扩展模块允许Python程序员创建wxW
indows类的实例,并且调用这些类的方法。
wxPython扩展模块试图近可能的将wxWindows的类的层次也镜像下来。这就是说在wxPython中有一个wxFrame的类,看上去,闻上去,尝上
去和行为上几乎同C ++版本中的wxFrame一样。
wxPython与C++版本如此接近,这样wxPython文档的大多数实际上是对C++文档中,wxPython与之不同地方的注解。其中还包含了一系列的
例子程序,和一系列帮助程序员开始使用w xPython的文档页
2
哪里可以得到wxPython
wxPython的最新版本可以在http://alldunn.com/wxPython/上找到。你可以从这个站点下载一个Win32系统的自安装软件,其中包含一个已
经生成好的扩展模块,H TML帮助格式文档,和一组示例程序。
也可以从这个站点获得Linux RPM,wxPython源码,原始的HTML文档,和其它站点的链接,邮件列表,wxPython FAQ,等等。
如果你想自已从源代码创建wxPython,你也需要wxWindows源代码,可以从http://www.wxwindows.org/得到。
下一步去哪里?
实践证明,学习的最好方法就是动手,接着做实验,观察得到的结果。所以你应该下载和安装wxPython,启动你常用的文本编辑器,准备
执行在下面几节所读到的东西。
一个简单的例子
应该对下面的小wxPython程序进行熟悉,当读到跟着的解释时,可以回过头来进行参考:
from wxPython.wx import *
class MyApp(wxApp):
def OnInit(self):
frame = wxFrame(NULL, -1, "Hello from wxPython")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
一个基本的wxPython程序要做的第一件事情就是导入整个wxPython库,使用 from wxPython import * 语句。这是编wxPython程序的通常习
惯,但是你可以根据需要明确地执行更多限制的导入。
每一个wxPython应用程序需要从wxApp派生出一个类,并且为其提供一个OnInit 方法。框架(即窗口)会调用这个方法作为自身初始化序
列的一部分。Oninit通常是用来创建窗口,和对于程序开始运行完全必需的操作。在例子中,你创建了一个没有父亲的框架,标题是“Hello
from wxPython ”,然后显示它。我们也可以在它的构造函数中为框架指定位置和大小,但是因为没有,所以这里使用了缺省值。OnInit方法
的最后两行可能对于所有应用程序来说都是一样的。SetTopWindow方法告诉wxWindows ,这个框架是应用程序主框架其中之一(在这个例子中
只有一个),并且你返回true来表明成功。当所有项层窗口实关闭,应用程序结束。
脚本的最后两行可能又是对于所有的wxPython应用程序是一样的。你创建了一个应用程序类的实例,并且调用它的MainLoop方法。
MainLoop是应用程序的心脏:在这里事情被处理,并且被发送到各个窗口,当最后一个窗口关闭后,它返回。幸运的是,wxWindows对你屏蔽了
各种GUI工具包在事件处理上的不同。
3
大多数情况下,你会想要定制应用程序的主框架,所以使用普通的wxFrame是不够的。你可能希望,从wxFrame派生出自已的类进行定制。下一
个例子定义了一个框架类,并且在应用程序中的O nInit方法中创建了一个实例。注意除了在OnInit中创建的类的名字,MyApp代码的其它部分
同以前的例子是一样的。
from wxPython.wx import *
ID_ABOUT = 101
ID_EXIT = 102
class MyFrame(wxFrame):
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title,wxDefaultPosition, wxSize(200, 150))
self.CreateStatusBar()
self.SetStatusText("This is the statusbar")
menu = wxMenu()
menu.Append(ID_ABOUT, "&About","More information about this program")
menu.AppendSeparator()
menu.Append(ID_EXIT, "E&xit", "Terminate the program")
menuBar = wxMenuBar()
menuBar.Append(menu, "&File");
self.SetMenuBar(menuBar)
class MyApp(wxApp):
def OnInit(self):
frame = MyFrame(NULL, -1, "Hello from wxPython")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
这个例子显示了一些wxFrame内建的一些功能。例如,为框架创建一个状态条只要简单地调用一个方法。框架本身会自动地管理它的位置,
大小,和绘制。另一方面,如果你想定制状态条,从你自已的wxStatusBar派生类创建实例,并将其附加到框架上。
在这个例子中也演示了创建一个简单的菜单条和一个下拉菜单。期待的菜单功能全部都支持:层叠子菜单,可核选的项,弹出菜单等等;
你要做的只是创建一个菜单对象,向它追加菜单项。菜单项可以是象这里显示的文本,或其它的菜单。每一项你可以有选择地指定一些简单的
帮助性文本,就象我们所做的。当菜单项被选中,这些文本会自动地显示在状态条上。
4
在wxPython中的事件上一个例子没有做的一件事情就是:展示如何让菜单自动做一些事情。如果你运行这个例子,并且从菜单中选中Exit,
什么都没有发生。下一个例子改正了这个小问题。
为了在wxPython中处理事件,任何方法(或同样方式的独立函数)都可以使用工具包中的帮助性函数与任意事件相连。wxPython也提供了
一个w xEvent 类,和一整串派生类,包含了事件的细节。每次当发生一个事件,一个方法被调用,一个从wxEvent 派生的对象被做为一参数传
递,事件对象的实际类型要依赖于事件的类型。wxSizeEvent用于当窗口改变大小,wxCommandEvent用于菜单选择和按钮点击,w xMouseEvent
用于(可以猜到)鼠标的移动,等等。
为了解决上一个例子中的小问题,你要做的就是,在MyFrame构造函数中增加两行,并且增加处理事件的一些方法。我们也演示了一个通用
对话框,w xMessageDialog。下面就是代码。
from wxPython.wx import *
ID_ABOUT = 101
ID_EXIT = 102
class MyFrame(wxFrame):
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title,wxDefaultPosition, wxSize(200, 150))
self.CreateStatusBar()
self.SetStatusText("This is the statusbar")
menu = wxMenu()
menu.Append(ID_ABOUT, "&About","More information about this program")
menu.AppendSeparator()
menu.Append(ID_EXIT, "E&xit", "Terminate the program")
menuBar = wxMenuBar()
menuBar.Append(menu, "&File");
self.SetMenuBar(menuBar)
EVT_MENU(self, ID_ABOUT, self.OnAbout)
EVT_MENU(self, ID_EXIT, self.TimeToQuit)
def OnAbout(self, event):
dlg = wxMessageDialog(self, "This sample program shows off\n"
"frames, menus, statusbars, and this\n""message dialog.",
"About Me", wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def TimeToQuit(self, event):
self.Close(true)
class MyApp(wxApp):
def OnInit(self):
frame = MyFrame(NULL, -1, "Hello from wxPython")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
在这里调用的EVT_MENU函数是一个帮助性函数,是为了将事件与方法连在一起。有时候,如果你将函数调用翻译成英语,它可以帮助我们
理解发生了什么。第一个说的是,“对于任一个菜单项选中事件,发送窗口本身和一个ID_ABOUT的ID号,调用self.OnAbout方法。”
有很多EVT_*帮助性函数,所有的都对应某个事件类型,或事件。下面将常用的一些事件列出。
请参阅wxPython文档了解更多的细节。
常见wxPython事件函数 事件函数事件描述
EVT_SIZE
由于用户干预或由程序实现,当一个窗口大小发生改变时发送给窗口。
EVT_MOVE
由于用户干预或由程序实现,当一个窗口被移动时发送给窗口。
EVT_CLOSE
当一个框架被要求关闭时发送给框架。除非关闭是强制性的,否则可以调用event.Veto(true)来取消关闭。
EVT_PAINT
无论何时当窗口的一部分需要重绘时发送给窗口。
EVT_CHAR
当窗口拥有输入焦点时,每产生非修改性(Shift键等等)按键时发送。
EVT_IDLE
这个事件会当系统没有处理其它事件时定期的发送。
EVT_LEFT_DOWN
鼠标左键按下。
EVT_LEFT_UP
鼠标左键抬起。
EVT_LEFT_DCLICK
鼠标左键双击。
EVT_MOTION
鼠标在移动。
EVT_SCROLL
滚动条被操作。这个事件其实是一组事件的集合,如果需要可以被单独捕捉。
EVT_BUTTON
按钮被点击。
EVT_MENU
菜单被选中
5
用Python创建一个Doubletalk浏览器
Ok, 现在让我们做些有用的东西,用这种方法学习更多关于wxPython框架的知识。就象其它的GUI工具包所展示的,我们将创建一个小型的
应用程序,围绕着Doubletalk类库(它允许浏览和编辑交易)。
MDI框架
我们打算实现一个多文档界面(译注,即MDI),子框架除了为独立的“文档”外,其中还包含着交易数据的不同视图。如同前面的例子,
要做的第一件事就是创建一个应用程序类,并且在它的O nInit方法中创建一个主框架:
from wxPython.wx import *
class DoubleTalkBrowserApp(wxApp):
def OnInit(self):
frame = MainFrame(NULL)
frame.Show(true)
self.SetTopWindow(frame)
return true
app = DoubleTalkBrowserApp(0)
app.MainLoop()
因为我们正在使用MDI,所以需要一个特别的类用来做为框架的基本类。这里的给出主程序框架的初始化方法的代码:
class MainFrame(wxMDIParentFrame):
title = "Doubletalk Browser - wxPython Edition"
def __init__(self, parent):
wxMDIParentFrame.__init__(self, parent, -1, self.title)
self.bookset = None
self.views = []
if wxPlatform == ’__WXMSW__’:
self.icon = wxIcon(’chart7.ico’, wxBITMAP_TYPE_ICO)
self.SetIcon(self.icon)
# 创建一个状态条,在右边显示时间和日期
sb = self.CreateStatusBar(2)
sb.SetStatusWidths([-1, 150])
self.timer = wxPyTimer(self.Notify)
self.timer.Start(1000)
self.Notify()
menu = self.MakeMenu(false)
self.SetMenuBar(menu)
menu.EnableTop(1, false)
EVT_MENU(self, ID_OPEN, self.OnMenuOpen)
EVT_MENU(self, ID_CLOSE, self.OnMenuClose)
EVT_MENU(self, ID_SAVE, self.OnMenuSave)
EVT_MENU(self, ID_SAVEAS,self.OnMenuSaveAs)
EVT_MENU(self, ID_EXIT, self.OnMenuExit)
EVT_MENU(self, ID_ABOUT, self.OnMenuAbout)
EVT_MENU(self, ID_ADD, self.OnAddTrans)
EVT_MENU(self, ID_JRNL, self.OnViewJournal)
EVT_MENU(self, ID_DTAIL, self.OnViewDetail)
EVT_CLOSE(self, self.OnCloseWindow)
很明显,我们没有展示全部的代码,但是随着我们一点一点地学习,我们将最终将其变得完整。
注意,使用wxMDIParentFrame作为MainFrame的基类。通过使用这个类,你会自动地获得为实现MDI应用程序的所有需要的东西,不需要关
心在外表后面发生了什么。wxMDIParentFrame 类有着与wxFrame类同样的接口,只是拥有一些额外的方法。通常将一个单文档接口程序改为一
个多文档程序只是象改变应用程序派生类的基类一样容易。对于wxMDIParentFrame 类,存在相对应的wxMDIChildFrame类,用于作文档窗口,
在后面就会看到。如果你需要处理MDI父窗口的客户区域(或背景区域),你可以使用 wxMDIClientWindow类。你可以使用它来在所有子窗口的
后面放置一个背景图像。
在文件对话框成功完成之后,要做的第一件事就是询问对话框被选中的路径名是什么,然后使用这个路径来修改框架的标题,然后打开
一个BookSet文件。
看一下后面一行。它重新使BookSet菜单有效,因为现在已经存在一个打开文件了。它实际是两行语句合成一句,相当于这两行:
menu = self.GetMenuBar()
menu.EnableTop(1, true)
因为当用户打开一个文件时,让他们确实地看到一些东西是有意义的,你应该创建并且显示其中一个视图,用上面的OnMenuOpen处理函数
中最后几行代码。在下面,我们会看到。
wxListCtrl
日志视图是由wxListCtrl组成,其中每一个交易都有一个单行的小计。这个控件放置在wxMDIChildFrame中,并且因为它是框架中唯一的东
西,所以不用担心设置或维护大小,框架会自动维护它。(不幸地是,因为某些平台在不同的时间发送第一个改变大小(r esize)事件,有时
候窗口显示时,它的子窗口大小会不正确。)
class JournalView(wxMDIChildFrame):
def __init__(self, parent, bookset, editID):
wxMDIChildFrame.__init__(self, parent, -1, "")
self.bookset = bookset
self.parent = parent
tID = wxNewId()
self.lc = wxListCtrl(self, tID, wxDefaultPosition,
wxDefaultSize, wxLC_REPORT)
## Forces a resize event to get around a minor bug...
self.SetSize(self.GetSize())
self.lc.InsertColumn(0, "Date")
self.lc.InsertColumn(1, "Comment")
self.lc.InsertColumn(2, "Amount")
self.currentItem = 0
EVT_LIST_ITEM_SELECTED(self, tID, self.OnItemSelected)
EVT_LEFT_DCLICK(self.lc, self.OnDoubleClick)
menu = parent.MakeMenu(true)
self.SetMenuBar(menu)
EVT_MENU(self, editID, self.OnEdit)
EVT_CLOSE(self, self.OnCloseWindow)
self.UpdateView()