[转贴][翻译]wxPython in Action - 巨蟒在行动 - 入门
2、给你的wxPython程序一个稳固的基础
房屋的基础是混凝土结构,它为其余的建造提供了坚固的基础。你的wxPython程序同样有一个基础,它由两个必要的对象组成,用于支持你的应用程序的其余部分。它们是应用程序对象和顶级窗口对象。适当地使用这两个对象将给你的wxPython应用程序一个稳固的开始并使得构造你的应用程序的其余部分更容易。
2.1 关于所要求的对象我们需要知道些什么?
让我们来说明一下这两个基础对象。这个应用程序对象管理主事件循环,主事件循环是你的wxPython程序的动力。启动主事件循环是应用程序对象的工作。没有应用程序对象,你的wxPython应用程序将不能运行。
顶级窗口通常管理最重要的数据,控制并呈现给用户。例如,在词处理程序中,主窗口是文档的显示部分,并很可能管理着该文档的一些数据。类似地,你的web浏览器的主窗口同时显示你所关注的页面并把该页作为一个数据对象管理。
下图显示了这两个基础对象和你的应用程序的其它部分这间的关系:
如图所示,这个应用程序对象拥有顶级窗口和主事件循环。顶级窗口管理其窗口中的组件和其它的你分配给它的数据对象。窗口和它的组件的触发事件基于用户的动作,并接受事件通知以便改变显示。
2.2 如何创建和使用一个应用程序对象?
任何wxPython应用程序都需要一个应用程序对象。这个应用程序对象必须是类wx.App或其定制的子类的一个实例。应用程序对象的主要目的是管理幕后的主事件循环。这个事件循环响应于窗口系统事件并分配它们给适当的事件处理器。这个应用程序对象对wxPython进程的管理如此的重要以至于在你的程序没有实例化一个应用程序对象之前你不能创建任何的wxPython图形对象。
父类wx.App也定义了一些属性,它们对整个应用程序是全局性的。很多时候,它们就是你对你的应用程序对象所需要的全部东西。假如你需要去管理另外的全局数据或连接(如一个数据库连接),你可以定制应用程序子类。在某些情况下,你可能想为专门的错误或事件处理而扩展这个主事件循环。然而,默认的事件循环几乎适合所有的你所要写的wxPython应用程序。
2.2.1 创建一个wx.App的子类
创建你自己的wx.App的子类是很简单的。当你开始你的应用程序的时候,创建你自己的wx.App的子类通常是一个好的想法,即使是你不定制任何功能。创建和使用一个wx.App子类,你需要执行四个步骤:
1、定义这个子类
2、在定义的子类中写一个OnInit()方法
3、在你的程序的主要部分创建这个类的一个实例
4、调用应用程序实例的MainLoop()方法。这个方法将程序的控制权转交给wxPython
我们在第一章中看到过OnInit()方法。它在应用程序开始时并在主事件循环开始前被wxPython系统调用。这个方法不要求参数并返回一个布尔值,如果所返回的值是False,则应用程序将立即退出。大多数情况下,你将想要该方法返回的结果为真。处理某些错误条件,退出可能是恰当的方法,诸如所一个所需的资源缺失。
由于OnInit()方法的存在,并且它是wxPython架构的一部分,所以任何关于你的定制的类的所需的初始化通常都由OnInit()方法管理,而不在Python的__init__方法中。如果由于某些原因你决定需要__init__方法,那么你必须在你的 __init__方法中调用父类的__init__方法,如下所示:
wx.App.__init__(self)
通常,你在OnInit()方法中将至少创建一个框架对象,并调用该框架的Show()方法。你也可以有选择地通过调用SetTopWindow()方法来为应用程序指定一个框架作为顶级窗口。顶级窗口被作为那些没有指定父窗口的对话框的默认父窗口。
何时省略wx.App的子类
你没有必要创建你自己的wx.App子类,你通常想这样做是为了能够在OnInit()方法中创建你的顶级框架。
通常,如果在系统中只有一个框架的话,避免创建一个wx.App子类是一个好的主意。在这种情况下,wxPython提供了一个方便的类wx.PySimpleApp。这个类提供了一个最基本的OnInit()方法,wx.PySimpleApp类定义如下:
class PySimpleApp(wx.App):
def __init__(self, redirect=False, filename=None,
useBestVisual=False, clearSigInt=True):
wx.App.__init__(self, redirect, filename, useBestVisual,
clearSigInt)
def OnInit(self):
return True
下面是wx.PySimpleApp一个简单用法:
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyNewFrame(None)
frame.Show(True)
app.MainLoop()
在上面这段代码的第一行,你创建了一个作为wx.PySimpleApp的实例的应用程序对象。由于我们在使用 wx.PySimpleApp类,所以我们没有定制OnInit方法。第二行我们定义了一个没有父亲的框架,它是一个顶级的框架。(很显然,这个MyNewFrame类需要在别处被定义)这第三行显示框架,最后一行调用应用程序主循环。
正如你所看到的,使用wx.PySimpleApp让你能够运行你的wxPython程序而无需创建你自己定制的应用程序类。如果你的应用程序十分简单的话,你应该只使用wx.PySimpleApp,且不需要任何其它的全局参数。
2.2.2 理解应用程序对象的生命周期
你的wxPython应用程序对象的生命周期开始于应用程序实例被创建时,在最后一个应用程序窗口被关闭时结束。这个没有必要与你的wxPython应用程序所在的Python脚本的开始和结束相对应。Python脚本可以在wxPython应用程序创建之前选择做一动作,并可以在wxPython应用程序的MainLoop()退出后做一些清理工作。然而所有的wxPython动作必须在应用程序对象的生命周期中执行。正如我们曾提到过的,这意味你的主框架对象在wx.App对象被创建之前不能被创建。(这就是为什么我们建议在OnInit()方法中创建顶级框架——因为这样一来,就确保了这个应用程序已经存在。)
下图所示,创建应用程序对象触发OnInit()方法并允许新的窗口对象被创建。在OnInit()之后,这个脚本调用 MainLoop()方法,通知wxPython事件现在正在被处理。在窗口被关闭之前应用程序继续它的事件处理。当所有顶级窗口被关闭后, MainLoop()函数返回同时应用程序对象被注销。这之后,这个脚本能够关闭其它的可能存丰的连接或线程。
2.3 如何定向wxPython程序的输出?
所有的Python程序都能够通过两种标准流来输出文本:分别是标准输出流sys.stdout和标准错误流sys.stderr。通常,Python脚本定向标准输出流到它所运行的控制台。然而,当你的应用程序对象被创建时,你可以决定使用wxPython控制标准流并重定向输出到一个窗口。在Windows下,这个重定向行为是wxPython的默认行为。而在Unix系统中,默认情况下,wxPython不控制这个标准流。在所有的系统中,当应用程序对象被创建的时候,重定向行为可以被明确地指定。我们推荐利用这个特性并总是指定重定向行为来避免不同平台上的不同行为产生的任何问题。
2.3.1 重定向输出
如果wxPython控制了标准流,那么经由任何方法发送到流的文本被重定向到一个wxPython的框架。在wxPyton应用程序开始之前或结束之后发送到流的文本将按照Python通常的方法处理(输出到控制台)。下例同时演示了应用程序的生命周期和stdout/stderr重定向:
#!/usr/bin/env python
import wx
import sys
class Frame(wx.Frame):
def __init__(self, parent, id, title):
print "Frame __init__"
wx.Frame.__init__(self, parent, id, title)
class App(wx.App):
def __init__(self, redirect=True, filename=None):
print "App __init__"
wx.App.__init__(self, redirect, filename)
def OnInit(self):
print "OnInit" #输出到stdout
self.frame = Frame(parent=None, id=-1, title='Startup') #创建框架
self.frame.Show()
self.SetTopWindow(self.frame)
print >> sys.stderr, "A pretend error message" #输出到stderr
return True
def OnExit(self):
print "OnExit"
if __name__ == '__main__':
app = App(redirect=True) #1 文本重定向从这开始
print "before MainLoop"
app.MainLoop() #2 进入主事件循环
print "after MainLoop"
说明:
#1 这行创建了应用程序对象。这行之后,所有发送到stderr或stdout的文本都可被wxPython重定向到一个框架。参数redirect=True决定了是否重定向。
#2 运行的时候,应用程序创建了一个空的框架和也生成了一个用于重定向输出的框架。图示如下:
注意:stdout和stderr都定向到这个窗口。
当你运行了这个程序之后,你将会看到你的控制台有下面的输出:
App __init__
after MainLoop
这第一行在框架被打开之前生成,第二行在框架被关闭之后生成。
通过观察控制台和框架的输出,我们可以跟踪应用程序的生命周期。
下面我们将上面的程序与图2.2作个比较,图中的"Start Script"对应于程序的 __main__语句。然后立即过渡到下一 “Application obect created",对应于程序的app = App(redirect=True)。应用程序实例的创建通过调用 wx.App.__init__()方法。然后是OnInit(),它被wxPython自动调用。从这里,程序跳转到 wx.Frame.__init__(),它是在wx.Frame被实例化时运行。最后控制转回到__main__语句,这里,MainLoop()被调用,对应于图中的"MainLoop() called"。主循环结束后,wx.App.OnExit()被wxPython调用,对应于图中 “Application object destroyed”。然后脚本的其余部分完成处理。
为什么来自OnExit()的消息既没显示在窗口中也没显示在控制台中呢?其实它是在窗口关闭之前显示在wxPython的框架中,但窗口消失太快,所以无法被屏幕捕获。
2.3.2 修改默认的重定向行为
为了修改这个行为,wxPython允许你在创建应用程序时设置两个参数。第一个参数是redirect,如果值为True,则重定向到框架,如果值为 False,则输出到控制台。如果参数redirect为True,那么第二个参数filename也能够被设置,这样的话,输出被重定向到 filename所指定的文件中而不定向到wxPython框架。因此,如果我们将上例中的app = App(redirect=True)改为 app = App(False),则输出将全部到控制台中:
App __init__
OnInit
Frame __init__
A pretend error message
before MainLoop
OnExit
after MainLoop
我们可以注意到OnExit()消息在这里显示出来了。
我们再作一个改变:
app = App(True, "output")
这将导致所有的应用程序创建后的输出重定向到名为output的文件中。而"App__init"和"after MainLoop"消息仍将发送到控制台,这是因为它们产生在wx.App对象控制流的时期之外。
2.4 如何关闭wxPython应用程序?
当你的应用程序的最后的顶级窗口被用户关闭时, wxPython应用程序就退出了。我们这里所说的顶层窗口是指任何没有父亲的框架,并不只是使用SetTopWindow()方法设计的框架。这包括任何由wxPython自身创建的框架。在我们重定向的例子中,wxPython应用程序在主框架和输出重定向的框架都被关闭后退出,仅管只有主框架是使用 SetTopWindow()登记的,尽管应用程序没有明确地创建这个输出重定向框架。要使用编程触发一个关闭,你可以在所有的这里所谓顶级窗口上调用 Close()方法。
2.4.1 管理正常的关闭
在关闭的过程期间, wxPython关心的是删除所有的它的窗口和释放它们的资源。你可以在退出过程中定义一个钩子来执行你自己的清理工作。由于你的wx.App子类的 OnExit()方法在最后一个窗口被关闭后且在wxPython的内在的清理过程之前被调用,你可以使用OnExit()方法来清理你创建的任何非 wxPython资源(例如一个数据库连接)。即使使用了wx.Exit()来关闭wxPython程序,OnExit()方法仍将被触发。
如果由于某种原因你想在最后的窗口被关闭后wxPython应用程序仍然可以继续,你可以使用wx.App的SetExitOnFrameDelete (flag)方法来改变默认的行为。如果flag参数设置为False,那么最后的窗口被关闭后wxPython应用程序仍然会继续运行。这意味着 wx.App实例将继续存活,并且事件循环将继续处理事件,比如这时你还可以创建所有新的这里所谓的顶级窗口。wxPython应用程序将保持存活直到全局函数wx.Exit()被明确地调用。
2.4.2 管理紧急关闭
你不能总是以一个可控的方法关闭你的程序。有时候,你需要立即结束应用程序并不考虑清理工作。例如一个必不可少的资源可能已被关闭或被损坏。如果系统正在关闭,你可能不能做所有的清理工作。
这里有两种在紧急情况下退出你的wxPython应用程序的方法。你可以调用wx.App的ExitMainLoop()方法。这个方法显式地使用主消息循环终止,使用控制离开MainLoop()函数。这通常将终止应用程序,这个方法实际上等同于关闭所有这里所谓顶级窗口。
你也可以调用全局方法wx.Exit()。正常使用情况下,两种方法我们都不推荐,因为它将导致一些清理函数被跳过。
有时候,你的应用程序由于一个控制之外的事件而需要关闭。例如操作系统的关闭或注销。在这种情况下,你的应用程序将试图做一些保存文档或关闭连接等等。如果你的应用程序为wx.EVT_QUERY_END_SESSION事件绑定了一个事件处理器,那么当wxPython得到关闭通知时这个事件处理器将被调用。这个event参数是wx.CloseEvent。我们可以通过关闭事件来否决关闭。这可以使用关闭事件的CanVeto()方法,CanVeto ()方法决定是否可以否决,Veto()执行否决。如果你不能成功地保存或关闭所有的资源,你可能想使用该方法。 wx.EVT_QUERY_END_SESSION事件的默认处理器调用顶级窗口的Close()方法,这将依次向顶层窗口发送wx.EVT_CLOSE 事件,这给了你控制关闭过程的另一选择。如果任何一个Close()方法返回False,那么应用程序将试图否决关闭。
2.5 如何创建和使用顶级窗口对象?
在你的应用程序中一个顶级窗口对象是一个窗口部件(通常是一个框架),它不被别的窗口部件所包含。顶级窗口对象通常是你的应用程序的主窗口,它包含用户与之交互的窗口部件和界面对象。当所有的顶级窗口被关闭时应用程序退出。
你的应用程序至少必须有一个顶级窗口对象。顶级窗口对象通常是类wx.Frame的子类,尽管它也可以是wx.Dialog的子类。大多数情况下,你将为了使用为你的应用程序定义定制的wx.Frame的子类。然而,这儿也存在一定数量的预定义的wx.Dialog的子类,它们提供了许多你可能会在一个应用程序中遇到的典型的对话框。
这儿可能有一个名称上的混淆,那就是“顶级窗口”。一般意义上的顶级窗口是指在你的应用程序中任何没有父容器的窗口部件。你的应用程序必须至少有一个,但是,只要你喜欢可以有多个。但是它们中只有一个可以通过使用SetTopWindow()被wxPython 作为主顶级窗口。如果你没有使用SetTopWindow()指定主顶级窗口,那么在wx.App的顶级窗口列表中的第一个框架将被认为是这个主顶级窗口。因此,明确地定义一个主顶级窗口不总是必要的,例如,你只有一个顶级窗口的时候。反复调用SetTopWindow()将反复改变当前的主顶级窗口,因为一个应用程序一次只能有一主顶级窗口。
2.5.1 使用wx.Frame
按照 wxPython中的说法,框架就是用户通常称的窗口。那就是说,框架是一个容器,用户可以将它在屏幕上任意移动,并可将它缩放,它通常包含诸如标题栏、菜单等等。在wxPython中,wx.Frame是所有框架的父类。这里也有少数专用的wx.Frame子类,你可以使用它们。
当你创建wx.Frame的子类时,你的类应该调用其父类的构造器wx.Frame.__init__()。wx.Frame的构造器所要求的参数如下:
wx.Frame(parent, id=-1, title="", pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame")
我们在别的窗口部件的构造器中将会看到类似的参数。参数的说明如下:
parent:框架的父窗口。对于顶级窗口,这个值是None。框架随其父窗口的销毁而销毁。取决于平台,框架可被限制只出现在父窗口的顶部。在多文档界面的情况下,子窗口被限制为只能在父窗口中移动和缩放。
id:关于新窗口的wxPython ID号。你可以明确地传递一个。或传递-1,这将导致wxPython自动生成一个新的ID。
title:窗口的标题。
pos:一个wx.Point对象,它指定这个新窗口的左上角在屏幕中的位置。在图形用户界面程序中,通常(0,0)是显示器的左上角。这个默认的(-1,-1)将让系统决定窗口的位置。
size:一个wx.Size对象,它指定这个窗口的初始尺寸。这个默认的(-1,-1)将让系统决定窗口的初始尺寸。
style:指定窗口的类型的常量。你可以使用或运算来组合它们。
name:框架的内在的名字。以后你可以使用它来寻找这个窗口。
记住,这些参数将被传递给父类的构造器方法:wx.Frame.__init__()。
创建wx.Frame子类的方法如下所示:
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "My Friendly Window",
(100, 100), (100, 100))
2.5.2 使用wxPython的ID
在wxPython 中,ID号是所有窗口部件的特征。在一个wxPython应用程序中,每个窗口部件都有一个窗口标识。在每一个框架内,ID号必须是唯一的,但是在框架之间你可以重用ID号。然而,我们建议你在你的整个应用程序中保持ID号的唯一性,以防止处理事件时产生错误和混淆。在wxPython中也有一些标准的预定义的ID号,它们有特定的意思(例如,wx.ID_OK和wx.ID_CANCEL是对话框中的OK和Cancel按钮的ID号)。在你的应用程序中重用标准的ID号一般没什么问题,只要你在预期的方式中使用它们。在wxPython中,ID号的最重要的用处是在指定的对象发生的事件和响应该事件的回调函数之间建立唯一的关联。
有三种方法来创建一个窗口部件使用的ID号:
1、明确地给构造器传递一个正的整数
2、使用wx.NewId()函数
3、传递一个全局常量wx.ID_ANY或-1给窗口部件的构造器
明确地选择ID号
第一个或最直接的方法是明确地给构造器传递一个正的整数作为该窗口部件的ID。如果你这样做了,你必须确保在一个框架内没有重复的ID或重用了一个预定义的常量。你可以通过调用wx.RegisterId()来确保在应用程序中wxPython不在别处使用你的ID。要防止你的程序使用相同的 wxPython ID,你应该避免使用全局常量wx.ID_LOWEST和wx.ID_HIGHEST之间的ID号。
使用全局性的NewID()函数
自己确保ID号的唯一性十分麻烦,你可以使用wx.NewId()函数让wxPython来为你创建ID:
id = wx.NewId()
frame = wx.Frame.__init__(None, id)
你也可以给窗口部件的构造器传递全局常量wx.ID_ANY或-1,然后wxPython将为你生成新的ID。然后你可以在需要这个ID时使用GetId()方法来得到它:
frame = wx.Frame.__init__(None, -1)
id = frame.GetId()
2.5.3 使用wx.Size和wx.Point
wx.Frame构造器的参数也引用了类wx.Size和wx.Point。这两个类在你的wxPython编程中将频繁被使用。
wx.Point类表示一个点或位置。构造器要求点的x和y值。如果不设置x,y值,则值默认为0。我们可以使用Set(x,y)和Get()函数来设置和得到x和y值。Get()函数返回一个元组。x和y值可以像下面这样作为属性被访问:
point = wx.Point(10, 12)
x = point.x
y = point.y
另外,wx.Point的实例可以像其它Python对象一样作加、减和比较运算,例如:
a = wx.Point(2, 3)
b = wx.Point(5, 7)
c = a + b
bigger = a > b
在wx.Point的实参中,坐标值一般为整数。如果你需要浮点数坐标,你可以使用类wx.RealPoint,它的用法如同wx.Point。
wx.Size类几乎和wx.Point完全相同,除了实参的名字是width和height。对wx.Size的操作与wx.Point一样。
在你的应用程序中当一个wx.Point或wx.Size实例被要求的时候(例如在另一个对象的构造器中),你不必显式地创建这个实例。你可以传递一个元组给构造器,wxPython将隐含地创建这个wx.Point或wx.Size实例:
frame = wx.Frame(None, -1, pos=(10, 10), size=(100, 100))
2.5.4 使用wx.Frame的样式
每个wxPython窗口部件都要求一个样式参数。这部分我们将讨论用于wx.Frame的样式。它们中的一些也适用于别的wxPython窗口部件。一些窗口部件也定义了一个SetStyle()方法,让你可以在该窗口部件创建后改变它的样式。所有的你能使用的样式元素都有一个常量标识符(如 wx.MINIMIZE_BOX)。要使用多个样式,你可以使用或运算符|。例如,wx.DEFAULT_FRAME_STYLE样式就被定义为如下几个基本样式的组合:
wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX | wx.RESIZE_BORDER |
wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX
要从一个合成的样式中去掉个别的样式,你可以使用^操作符。例如要创建一个默认样式的窗口,但要求用户不能缩放和改变窗口的尺寸,你可以这样做:
wx.DEFAULT_FRAME_STYLE ^ (wx.RESIZE_BORDER | wx.MINIMIZE_BOX |wx.MAXIMIZE_BOX)
如果你不慎使用了&操作符,那么将得到一个没有样式的、无边框图的、不能移动、不能改变尺寸和不能关闭的帧。
下表2.2列出了用于wx.Frame的最重要的样式:
wx.CAPTION:在框架上增加一个标题栏,它显示该框架的标题属性。
wx.CLOSE_BOX:指示系统在框架的标题栏上显示一个关闭框,使用系统默认的位置和样式。
wx.DEFAULT_FRAME_STYLE:默认样式。
wx.FRAME_SHAPED:用这个样式创建的框架可以使用SetShape()方法去创建一个非矩形的窗口。
wx.FRAME_TOOL_WINDOW:通过给框架一个比正常更小的标题栏,使框架看起来像一个工具框窗口。在Windows下,使用这个样式创建的框架不会出现在显示所有打开窗口的任务栏上。
wx.MAXIMIZE_BOX:指示系统在框架的标题栏上显示一个最大化框,使用系统默认的位置和样式。
wx.MINIMIZE_BOX:指示系统在框架的标题栏上显示一个最小化框,使用系统默认的位置和样式。
wx.RESIZE_BORDER:给框架增加一个可以改变尺寸的边框。
wx.SIMPLE_BORDER:没有装饰的边框。不能工作在所有平台上。
wx.SYSTEM_MENU:增加系统菜单(带有关闭、移动、改变尺寸等功能)和关闭框到这个窗口。在系统菜单中的改变尺寸和关闭功能的有效性依赖于wx.MAXIMIZE_BOX, wx.MINIMIZE_BOX和wx.CLOSE_BOX样式是否被应用。
下面的四张图显示了几个通常的框架的样式。
图2.4是使用wx.DEFAULT_STYLE创建的。
图2.5是使用wx.DEFAULT_FRAME_STYLE ^ (wx.RESIZE_BORDER | wx.MINIMIZE_BOX |wx.MAXIMIZE_BOX)组合样式创建的。
图2.6使用的样式是wx.DEFAULT_FRAME_STYLE | wx.FRAME_TOOL_WINDOW。
图2.7使用了扩展样式 wx.help.FRAME_EX_CONTEXTHELP。
2.6 如何为一个框架增加对象和子窗口?
我们已经说明了如何创建wx.Frame对象,但是创建后的是空的。本节我们将介绍在你的框架中插入对象与子窗口的基础,以便与用户交互。
2.6.1 给框架增加窗口部件
图2.8显示了一个定制的wx.Frame的子类,名为InsertFrame。当点击close按钮时,这个窗口将关闭且应用程序将退出。例2.3定义了子类InsertFrame。
例2.3
#!/usr/bin/env python
import wx
class InsertFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
panel = wx.Panel(self) #创建画板
button = wx.Button(panel, label="Close", pos=(125, 10),
size=(50, 50)) #将按钮添加到画板
#绑定按钮的单击事件
self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button)
#绑定窗口的关闭事件
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
def OnCloseMe(self, event):
self.Close(True)
def OnCloseWindow(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = InsertFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
类InsertFrame 的方法__init__创建了两子窗口。第一个是wx.Panel,它是其它窗口的容器,它自身也有一点功能。第二个是wx.Button,它是一个平常按钮。接下来,按钮的单击事件和窗口的关闭事件被绑定到了相应的函数,当事件发生时这相应的函数将被调用执行。
大多数情况下,你将创建一个与你的wx.Frame大小一样的wx.Panel实例以容纳你的框架上的所有的内容。这样做可以让定制的窗口内容与其他如工具栏和状态栏分开。 通过tab按钮,可以遍历wx.Panel中的元素,wx.Frame不能。
你不必像使用别的UI工具包那样,你不需要显式调用一个增加方法来向双亲中插入一个子窗口。在wxPython中,你只需在子窗口被创建时指定父窗口,这个子窗口就隐式地增加到父对象中了,例如例2.3所示。
你可能想知道在例2.3中,为什么wx.Button被创建时使用了明确的位置和尺寸,而wx.Panel没有。在wxPython中,如果只有一个子窗口的框架被创建,那么那个子窗口(例2.3中是wx.Panel)被自动重新调整尺寸去填满该框架的客户区域。这个自动调整尺寸将覆盖关于这个子窗口的任何位置和尺寸信息,尽管关于子窗口的信息已被指定,这些信息将被忽略。这个自动调整尺寸仅适用于框架内或对话框内的只有唯一元素的情况。这里按钮是 panel的元素,而不是框架的,所以要使用指定的尺寸和位置。如果没有为这个按钮指定尺寸和位置,它将使用默认的位置(panel的左上角)和基于按钮标签的长度的尺寸。
显式地指定所有子窗口的位置和尺寸是十分乏味的。更重要的是,当用户调整窗口大小的时候,这使得子窗口的位置和大小不能作相应调整。为了解决这两个问题,wxPython使用了称为sizers的对象来管理子窗口的复杂布局。
2.6.2 给帧增加菜单栏、工具栏和状态栏。
图2.9显示了一个有菜单栏、工具栏和状态栏的帧。
例2.4显示了__init__方法,它用这三个子窗口装饰了一个简单的窗口。
例2.4
#!/usr/bin/env python
import wx
import images
class ToolbarFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Toolbars', status
size=(300, 200))
panel = wx.Panel(self)
panel.SetBackgroundColour('White')
statusBar = self.CreateStatusBar() #1 创建状态栏
toolbar = self.CreateToolBar() #2 创建工具栏
toolbar.AddSimpleTool(wx.NewId(), images.getNewBitmap(),
"New", "Long help for 'New'") #3 给工具栏增加一个工具
toolbar.Realize() #4 准备显示工具栏
menuBar = wx.MenuBar() # 创建菜单栏
# 创建两个菜单
menu1 = wx.Menu()
menuBar.Append(menu1, "&File")
menu2 = wx.Menu()
#6 创建菜单的项目
menu2.Append(wx.NewId(), "&Copy", "Copy in status bar")
menu2.Append(wx.NewId(), "C&ut", "")
menu2.Append(wx.NewId(), "Paste", "")
menu2.AppendSeparator()
menu2.Append(wx.NewId(), "&Options...", "Display Options")
menuBar.Append(menu2, "&Edit") # 在菜单栏上附上菜单
self.SetMenuBar(menuBar) # 在框架上附上菜单栏
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = ToolbarFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
说明:
#1:这行创建了一个状态栏,它是类wx.StatusBar的实例。它被放置在框架的底部,宽度与框架相同,高度由操作系统决定。状态栏的目的是显示在应用程序中被各种事件所设置的文本。
#2:创建了一个wx.ToolBar的实例,它是命令按钮的容器。它被自动放置在框架的顶部
#3:有两种方法来为你工具栏增加工具,这行使用了参数较少的一种:AddSimpleTool()。参数分别是ID,位图,该工具的短的帮助提示文本,显示在状态栏中的该工具的长的帮助文本信息。(此刻不要考虑位图从哪儿来)
#4:Realize()方法告诉工具栏这些工具按钮应该被放置在哪儿。这是必须的。
#6:创建菜单的项目,其中参数分别代表ID,选项的文本,当鼠标位于其上时显示在状态栏的文本。
2.7 如何使用一般的对话框?
wxPython提供了一套丰富的预定义的对话框。这部分,我们将讨论三种用对话框得到来自用户的信息:
1、消息对话框
2、文本输入对话框
3、从一个列表中选择
在wxPython中有许多别的标准对话框,包括文件选择器、色彩选择器、进度对话框、打印设置和字体选择器。这些将在第9章介绍。
消息对话框
与用户通信最基本的机制是wx.MessageDialog,它是一个简单的提示框。wx.MessageDialog可用作一个简单的OK框或yes/no对话框。下面的片断显示了yes/no对话框:
dlg = wx.MessageDialog(None, 'Is this the coolest thing ever!',
'MessageDialog', wx.YES_NO | wx.ICON_QUESTION)
result = dlg.ShowModal()
dlg.Destroy()
显示结果如图2.10所示:
wx.MessageDialog参数如下:
wx.MessageDialog(parent, message,
caption="Message box",
style=wx.OK | wx.CANCEL,
pos=wx.DefaultPosition)
参数说明:
parent:对话框的父窗口,如果对话框是顶级的则为None。
message:显示在对话框中的字符串。
caption:显示在对话框标题栏上的字符串。
style:对话框中按钮的样式。
pos:对话框出现的位置。
ShowModal ()方法将对话框以模式框架的方式显示,这意味着在对话框关闭之前,应用程序中的别的窗口不能响应用户事件。ShowModal()方法的返回值是一个整数,对于wx.MessageDialog,返回值是下面常量之一: wx.ID_YES, wx.ID_NO, wx.ID_CANCEL, wx.ID_OK。
文本输入对话框
如果你想从用户那里得到单独一行文本,你可能使用类wx.TextEntryDialog。下面的片断创建了一个文本输入域,当用户单击OK按钮退出时,获得用户输入的值:
dlg = wx.TextEntryDialog(None, "Who is buried in Grant's tomb?",
'A Question', 'Cary Grant')
if dlg.ShowModal() == wx.ID_OK:
response = dlg.GetValue()
图2.11显示了上面这个对话框:
上面的wx.TextEntryDialog的参数按顺序说明是,父窗口,显示在窗口中的文本标签,窗口的标题(默认是 “Please enter text”),输入域中的默认值。同样它也有一个样式参数,默认是wx.OK | wx.CANCEL。与 wx.MessageDialog一样,ShowModal()方法返回所按下的按钮的ID。GetValue()方法得到用户输入在文本域中的值(这有一个相应的SetValue()方法让你可以改变文本域中的值)。
从一个列表中选择
你可以让用户只能从你所提供的列表中选择,你可以使用类wx.SingleChoiceDialog。下面是一个简单的用法:
dlg = wx.SingleChoiceDialog(None,
'What version of Python are you using?',
'Single Choice',
['1.5.2', '2.0', '2.1.3', '2.2', '2.3.1'],
if dlg.ShowModal() == wx.ID_OK:
response = dlg.GetStringSelection()
图2.12显示了上面代码片断的结果。
wx.SingleChoiceDialog的参数类似于文本输入对话框,只是以字符串的列表代替了默认的字符串文本。要得到所选择的结果有两种方法, GetSelection()方法返回用户选项的索引,而GetStringSelection()返回实际所选的字符串。
2.8 一些最常见的错误现象及解决方法?
有一些错误它们可能会发生在你的wxPython应用程序对象或初始的顶级窗口在创建时,这些错误可能是很难诊断的。下面我们列出一些最常见的错误现象及解决方法:
错误现象:
程序启动时提示“unable to import module wx。”
原因:
wxPython模块不在你的PYTHONPATH中。这意味着wxPython没有被正确地安装。如果你的系统上有多个版本的Python,wxPython可能安装在了你没有使用的Python版本中。
解决方法:
首先,确定你的系统上安装了哪些版本的Python。在Unix系统上,使用which python命令将告诉你默认的安装。在Windows系统上,如果wxPython被安装到了相应的Python版本中,它将位于<python-home>/Lib/site-packages子目录下。然后重装wxPython。
错误现象:
应用程序启动时立即崩溃,或崩溃后出现一空白窗口。
原因:
在wx.App创建之前,创建或使用了一个wxPython对象。
解决方法:
在启动你的脚本时立即创建wx.App对象。
错误现象:
顶级窗口被创建同时又立刻关闭。应用程序立即退出。
原因:
没有调用wx.App的MainLoop()方法。
解决方法:
在你的所有设置完成后调用MainLoop()方法。
错误现象:
顶级窗口被创建同时又立刻关闭。应用程序立即退出。但我调用了MainLoop()方法。
原因:
你的应用程序的OnInit()方法中有错误,或OnInit()方法调用了某些方法(如帧的__init__()方法)。
解决方法:
在MainLoop()被调用之前出现错误的话,这将触发一个异常且程序退出。如果你的应用程序设置了重定向输出到窗口,那么那些窗口将一闪而过,你不能看到显示在窗口中的错误信息。这种情况下,你要使用 redirect=False关闭重定向选项,以便看到错误提示。
2.9 总结
1、wxPython程序的实现基于两个必要的对象:应用程序对象和顶级窗口。任何wxPython应用程序都需要去实例化一个wx.App,并且至少有一个顶级窗口。
2、应用程序对象包含OnInit()方法,它在启动时被调用。在这个方法中,通常要初始化框架和别的全局对象。wxPython应用程序通常在它的所有的顶级窗口被关闭或主事件循环退出时结束。
3、应用程序对象也控制wxPython文本输出的位置。默认情况下,wxPython重定向stdout和stderr到一个特定的窗口。这个行为使得诊断启动时产生的错误变得困难了。但是我们可以通过让wxPython把错误消息发送到一个文件或控制台窗口来解决。
4、一个wxPython应用程序通常至少有一个wx.Frame的子类。一个wx.Frame对象可以使用style参数来创建组合的样式。每个 wxWidget对象,包括框架,都有一个ID,这个ID可以被应用程序显式地赋值或由wxPython生成。子窗口是框架的内容,框架是它的双亲。通常,一个框架包含一个单一的wx.Panel,更多的子窗口被放置在这个Panel中。框架的唯一的子窗口的尺寸自动随其父框架的尺寸的改变而改变。框架有明确的关于管理菜单栏、工具栏和状态栏的机制。
5、尽管你将使用框架做任何复杂的事情,但当你想简单而快速地得到来自用户的信息时,你可以给用户显示一个标准的对话窗口。对于很多任务都有标准的对话框,包括警告框、简单的文本输入框和列表选择框等等。
房屋的基础是混凝土结构,它为其余的建造提供了坚固的基础。你的wxPython程序同样有一个基础,它由两个必要的对象组成,用于支持你的应用程序的其余部分。它们是应用程序对象和顶级窗口对象。适当地使用这两个对象将给你的wxPython应用程序一个稳固的开始并使得构造你的应用程序的其余部分更容易。
2.1 关于所要求的对象我们需要知道些什么?
让我们来说明一下这两个基础对象。这个应用程序对象管理主事件循环,主事件循环是你的wxPython程序的动力。启动主事件循环是应用程序对象的工作。没有应用程序对象,你的wxPython应用程序将不能运行。
顶级窗口通常管理最重要的数据,控制并呈现给用户。例如,在词处理程序中,主窗口是文档的显示部分,并很可能管理着该文档的一些数据。类似地,你的web浏览器的主窗口同时显示你所关注的页面并把该页作为一个数据对象管理。
下图显示了这两个基础对象和你的应用程序的其它部分这间的关系:
如图所示,这个应用程序对象拥有顶级窗口和主事件循环。顶级窗口管理其窗口中的组件和其它的你分配给它的数据对象。窗口和它的组件的触发事件基于用户的动作,并接受事件通知以便改变显示。
2.2 如何创建和使用一个应用程序对象?
任何wxPython应用程序都需要一个应用程序对象。这个应用程序对象必须是类wx.App或其定制的子类的一个实例。应用程序对象的主要目的是管理幕后的主事件循环。这个事件循环响应于窗口系统事件并分配它们给适当的事件处理器。这个应用程序对象对wxPython进程的管理如此的重要以至于在你的程序没有实例化一个应用程序对象之前你不能创建任何的wxPython图形对象。
父类wx.App也定义了一些属性,它们对整个应用程序是全局性的。很多时候,它们就是你对你的应用程序对象所需要的全部东西。假如你需要去管理另外的全局数据或连接(如一个数据库连接),你可以定制应用程序子类。在某些情况下,你可能想为专门的错误或事件处理而扩展这个主事件循环。然而,默认的事件循环几乎适合所有的你所要写的wxPython应用程序。
2.2.1 创建一个wx.App的子类
创建你自己的wx.App的子类是很简单的。当你开始你的应用程序的时候,创建你自己的wx.App的子类通常是一个好的想法,即使是你不定制任何功能。创建和使用一个wx.App子类,你需要执行四个步骤:
1、定义这个子类
2、在定义的子类中写一个OnInit()方法
3、在你的程序的主要部分创建这个类的一个实例
4、调用应用程序实例的MainLoop()方法。这个方法将程序的控制权转交给wxPython
我们在第一章中看到过OnInit()方法。它在应用程序开始时并在主事件循环开始前被wxPython系统调用。这个方法不要求参数并返回一个布尔值,如果所返回的值是False,则应用程序将立即退出。大多数情况下,你将想要该方法返回的结果为真。处理某些错误条件,退出可能是恰当的方法,诸如所一个所需的资源缺失。
由于OnInit()方法的存在,并且它是wxPython架构的一部分,所以任何关于你的定制的类的所需的初始化通常都由OnInit()方法管理,而不在Python的__init__方法中。如果由于某些原因你决定需要__init__方法,那么你必须在你的 __init__方法中调用父类的__init__方法,如下所示:
wx.App.__init__(self)
通常,你在OnInit()方法中将至少创建一个框架对象,并调用该框架的Show()方法。你也可以有选择地通过调用SetTopWindow()方法来为应用程序指定一个框架作为顶级窗口。顶级窗口被作为那些没有指定父窗口的对话框的默认父窗口。
何时省略wx.App的子类
你没有必要创建你自己的wx.App子类,你通常想这样做是为了能够在OnInit()方法中创建你的顶级框架。
通常,如果在系统中只有一个框架的话,避免创建一个wx.App子类是一个好的主意。在这种情况下,wxPython提供了一个方便的类wx.PySimpleApp。这个类提供了一个最基本的OnInit()方法,wx.PySimpleApp类定义如下:
class PySimpleApp(wx.App):
def __init__(self, redirect=False, filename=None,
useBestVisual=False, clearSigInt=True):
wx.App.__init__(self, redirect, filename, useBestVisual,
clearSigInt)
def OnInit(self):
return True
下面是wx.PySimpleApp一个简单用法:
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyNewFrame(None)
frame.Show(True)
app.MainLoop()
在上面这段代码的第一行,你创建了一个作为wx.PySimpleApp的实例的应用程序对象。由于我们在使用 wx.PySimpleApp类,所以我们没有定制OnInit方法。第二行我们定义了一个没有父亲的框架,它是一个顶级的框架。(很显然,这个MyNewFrame类需要在别处被定义)这第三行显示框架,最后一行调用应用程序主循环。
正如你所看到的,使用wx.PySimpleApp让你能够运行你的wxPython程序而无需创建你自己定制的应用程序类。如果你的应用程序十分简单的话,你应该只使用wx.PySimpleApp,且不需要任何其它的全局参数。
2.2.2 理解应用程序对象的生命周期
你的wxPython应用程序对象的生命周期开始于应用程序实例被创建时,在最后一个应用程序窗口被关闭时结束。这个没有必要与你的wxPython应用程序所在的Python脚本的开始和结束相对应。Python脚本可以在wxPython应用程序创建之前选择做一动作,并可以在wxPython应用程序的MainLoop()退出后做一些清理工作。然而所有的wxPython动作必须在应用程序对象的生命周期中执行。正如我们曾提到过的,这意味你的主框架对象在wx.App对象被创建之前不能被创建。(这就是为什么我们建议在OnInit()方法中创建顶级框架——因为这样一来,就确保了这个应用程序已经存在。)
下图所示,创建应用程序对象触发OnInit()方法并允许新的窗口对象被创建。在OnInit()之后,这个脚本调用 MainLoop()方法,通知wxPython事件现在正在被处理。在窗口被关闭之前应用程序继续它的事件处理。当所有顶级窗口被关闭后, MainLoop()函数返回同时应用程序对象被注销。这之后,这个脚本能够关闭其它的可能存丰的连接或线程。
2.3 如何定向wxPython程序的输出?
所有的Python程序都能够通过两种标准流来输出文本:分别是标准输出流sys.stdout和标准错误流sys.stderr。通常,Python脚本定向标准输出流到它所运行的控制台。然而,当你的应用程序对象被创建时,你可以决定使用wxPython控制标准流并重定向输出到一个窗口。在Windows下,这个重定向行为是wxPython的默认行为。而在Unix系统中,默认情况下,wxPython不控制这个标准流。在所有的系统中,当应用程序对象被创建的时候,重定向行为可以被明确地指定。我们推荐利用这个特性并总是指定重定向行为来避免不同平台上的不同行为产生的任何问题。
2.3.1 重定向输出
如果wxPython控制了标准流,那么经由任何方法发送到流的文本被重定向到一个wxPython的框架。在wxPyton应用程序开始之前或结束之后发送到流的文本将按照Python通常的方法处理(输出到控制台)。下例同时演示了应用程序的生命周期和stdout/stderr重定向:
#!/usr/bin/env python
import wx
import sys
class Frame(wx.Frame):
def __init__(self, parent, id, title):
print "Frame __init__"
wx.Frame.__init__(self, parent, id, title)
class App(wx.App):
def __init__(self, redirect=True, filename=None):
print "App __init__"
wx.App.__init__(self, redirect, filename)
def OnInit(self):
print "OnInit" #输出到stdout
self.frame = Frame(parent=None, id=-1, title='Startup') #创建框架
self.frame.Show()
self.SetTopWindow(self.frame)
print >> sys.stderr, "A pretend error message" #输出到stderr
return True
def OnExit(self):
print "OnExit"
if __name__ == '__main__':
app = App(redirect=True) #1 文本重定向从这开始
print "before MainLoop"
app.MainLoop() #2 进入主事件循环
print "after MainLoop"
说明:
#1 这行创建了应用程序对象。这行之后,所有发送到stderr或stdout的文本都可被wxPython重定向到一个框架。参数redirect=True决定了是否重定向。
#2 运行的时候,应用程序创建了一个空的框架和也生成了一个用于重定向输出的框架。图示如下:
注意:stdout和stderr都定向到这个窗口。
当你运行了这个程序之后,你将会看到你的控制台有下面的输出:
App __init__
after MainLoop
这第一行在框架被打开之前生成,第二行在框架被关闭之后生成。
通过观察控制台和框架的输出,我们可以跟踪应用程序的生命周期。
下面我们将上面的程序与图2.2作个比较,图中的"Start Script"对应于程序的 __main__语句。然后立即过渡到下一 “Application obect created",对应于程序的app = App(redirect=True)。应用程序实例的创建通过调用 wx.App.__init__()方法。然后是OnInit(),它被wxPython自动调用。从这里,程序跳转到 wx.Frame.__init__(),它是在wx.Frame被实例化时运行。最后控制转回到__main__语句,这里,MainLoop()被调用,对应于图中的"MainLoop() called"。主循环结束后,wx.App.OnExit()被wxPython调用,对应于图中 “Application object destroyed”。然后脚本的其余部分完成处理。
为什么来自OnExit()的消息既没显示在窗口中也没显示在控制台中呢?其实它是在窗口关闭之前显示在wxPython的框架中,但窗口消失太快,所以无法被屏幕捕获。
2.3.2 修改默认的重定向行为
为了修改这个行为,wxPython允许你在创建应用程序时设置两个参数。第一个参数是redirect,如果值为True,则重定向到框架,如果值为 False,则输出到控制台。如果参数redirect为True,那么第二个参数filename也能够被设置,这样的话,输出被重定向到 filename所指定的文件中而不定向到wxPython框架。因此,如果我们将上例中的app = App(redirect=True)改为 app = App(False),则输出将全部到控制台中:
App __init__
OnInit
Frame __init__
A pretend error message
before MainLoop
OnExit
after MainLoop
我们可以注意到OnExit()消息在这里显示出来了。
我们再作一个改变:
app = App(True, "output")
这将导致所有的应用程序创建后的输出重定向到名为output的文件中。而"App__init"和"after MainLoop"消息仍将发送到控制台,这是因为它们产生在wx.App对象控制流的时期之外。
2.4 如何关闭wxPython应用程序?
当你的应用程序的最后的顶级窗口被用户关闭时, wxPython应用程序就退出了。我们这里所说的顶层窗口是指任何没有父亲的框架,并不只是使用SetTopWindow()方法设计的框架。这包括任何由wxPython自身创建的框架。在我们重定向的例子中,wxPython应用程序在主框架和输出重定向的框架都被关闭后退出,仅管只有主框架是使用 SetTopWindow()登记的,尽管应用程序没有明确地创建这个输出重定向框架。要使用编程触发一个关闭,你可以在所有的这里所谓顶级窗口上调用 Close()方法。
2.4.1 管理正常的关闭
在关闭的过程期间, wxPython关心的是删除所有的它的窗口和释放它们的资源。你可以在退出过程中定义一个钩子来执行你自己的清理工作。由于你的wx.App子类的 OnExit()方法在最后一个窗口被关闭后且在wxPython的内在的清理过程之前被调用,你可以使用OnExit()方法来清理你创建的任何非 wxPython资源(例如一个数据库连接)。即使使用了wx.Exit()来关闭wxPython程序,OnExit()方法仍将被触发。
如果由于某种原因你想在最后的窗口被关闭后wxPython应用程序仍然可以继续,你可以使用wx.App的SetExitOnFrameDelete (flag)方法来改变默认的行为。如果flag参数设置为False,那么最后的窗口被关闭后wxPython应用程序仍然会继续运行。这意味着 wx.App实例将继续存活,并且事件循环将继续处理事件,比如这时你还可以创建所有新的这里所谓的顶级窗口。wxPython应用程序将保持存活直到全局函数wx.Exit()被明确地调用。
2.4.2 管理紧急关闭
你不能总是以一个可控的方法关闭你的程序。有时候,你需要立即结束应用程序并不考虑清理工作。例如一个必不可少的资源可能已被关闭或被损坏。如果系统正在关闭,你可能不能做所有的清理工作。
这里有两种在紧急情况下退出你的wxPython应用程序的方法。你可以调用wx.App的ExitMainLoop()方法。这个方法显式地使用主消息循环终止,使用控制离开MainLoop()函数。这通常将终止应用程序,这个方法实际上等同于关闭所有这里所谓顶级窗口。
你也可以调用全局方法wx.Exit()。正常使用情况下,两种方法我们都不推荐,因为它将导致一些清理函数被跳过。
有时候,你的应用程序由于一个控制之外的事件而需要关闭。例如操作系统的关闭或注销。在这种情况下,你的应用程序将试图做一些保存文档或关闭连接等等。如果你的应用程序为wx.EVT_QUERY_END_SESSION事件绑定了一个事件处理器,那么当wxPython得到关闭通知时这个事件处理器将被调用。这个event参数是wx.CloseEvent。我们可以通过关闭事件来否决关闭。这可以使用关闭事件的CanVeto()方法,CanVeto ()方法决定是否可以否决,Veto()执行否决。如果你不能成功地保存或关闭所有的资源,你可能想使用该方法。 wx.EVT_QUERY_END_SESSION事件的默认处理器调用顶级窗口的Close()方法,这将依次向顶层窗口发送wx.EVT_CLOSE 事件,这给了你控制关闭过程的另一选择。如果任何一个Close()方法返回False,那么应用程序将试图否决关闭。
2.5 如何创建和使用顶级窗口对象?
在你的应用程序中一个顶级窗口对象是一个窗口部件(通常是一个框架),它不被别的窗口部件所包含。顶级窗口对象通常是你的应用程序的主窗口,它包含用户与之交互的窗口部件和界面对象。当所有的顶级窗口被关闭时应用程序退出。
你的应用程序至少必须有一个顶级窗口对象。顶级窗口对象通常是类wx.Frame的子类,尽管它也可以是wx.Dialog的子类。大多数情况下,你将为了使用为你的应用程序定义定制的wx.Frame的子类。然而,这儿也存在一定数量的预定义的wx.Dialog的子类,它们提供了许多你可能会在一个应用程序中遇到的典型的对话框。
这儿可能有一个名称上的混淆,那就是“顶级窗口”。一般意义上的顶级窗口是指在你的应用程序中任何没有父容器的窗口部件。你的应用程序必须至少有一个,但是,只要你喜欢可以有多个。但是它们中只有一个可以通过使用SetTopWindow()被wxPython 作为主顶级窗口。如果你没有使用SetTopWindow()指定主顶级窗口,那么在wx.App的顶级窗口列表中的第一个框架将被认为是这个主顶级窗口。因此,明确地定义一个主顶级窗口不总是必要的,例如,你只有一个顶级窗口的时候。反复调用SetTopWindow()将反复改变当前的主顶级窗口,因为一个应用程序一次只能有一主顶级窗口。
2.5.1 使用wx.Frame
按照 wxPython中的说法,框架就是用户通常称的窗口。那就是说,框架是一个容器,用户可以将它在屏幕上任意移动,并可将它缩放,它通常包含诸如标题栏、菜单等等。在wxPython中,wx.Frame是所有框架的父类。这里也有少数专用的wx.Frame子类,你可以使用它们。
当你创建wx.Frame的子类时,你的类应该调用其父类的构造器wx.Frame.__init__()。wx.Frame的构造器所要求的参数如下:
wx.Frame(parent, id=-1, title="", pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame")
我们在别的窗口部件的构造器中将会看到类似的参数。参数的说明如下:
parent:框架的父窗口。对于顶级窗口,这个值是None。框架随其父窗口的销毁而销毁。取决于平台,框架可被限制只出现在父窗口的顶部。在多文档界面的情况下,子窗口被限制为只能在父窗口中移动和缩放。
id:关于新窗口的wxPython ID号。你可以明确地传递一个。或传递-1,这将导致wxPython自动生成一个新的ID。
title:窗口的标题。
pos:一个wx.Point对象,它指定这个新窗口的左上角在屏幕中的位置。在图形用户界面程序中,通常(0,0)是显示器的左上角。这个默认的(-1,-1)将让系统决定窗口的位置。
size:一个wx.Size对象,它指定这个窗口的初始尺寸。这个默认的(-1,-1)将让系统决定窗口的初始尺寸。
style:指定窗口的类型的常量。你可以使用或运算来组合它们。
name:框架的内在的名字。以后你可以使用它来寻找这个窗口。
记住,这些参数将被传递给父类的构造器方法:wx.Frame.__init__()。
创建wx.Frame子类的方法如下所示:
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "My Friendly Window",
(100, 100), (100, 100))
2.5.2 使用wxPython的ID
在wxPython 中,ID号是所有窗口部件的特征。在一个wxPython应用程序中,每个窗口部件都有一个窗口标识。在每一个框架内,ID号必须是唯一的,但是在框架之间你可以重用ID号。然而,我们建议你在你的整个应用程序中保持ID号的唯一性,以防止处理事件时产生错误和混淆。在wxPython中也有一些标准的预定义的ID号,它们有特定的意思(例如,wx.ID_OK和wx.ID_CANCEL是对话框中的OK和Cancel按钮的ID号)。在你的应用程序中重用标准的ID号一般没什么问题,只要你在预期的方式中使用它们。在wxPython中,ID号的最重要的用处是在指定的对象发生的事件和响应该事件的回调函数之间建立唯一的关联。
有三种方法来创建一个窗口部件使用的ID号:
1、明确地给构造器传递一个正的整数
2、使用wx.NewId()函数
3、传递一个全局常量wx.ID_ANY或-1给窗口部件的构造器
明确地选择ID号
第一个或最直接的方法是明确地给构造器传递一个正的整数作为该窗口部件的ID。如果你这样做了,你必须确保在一个框架内没有重复的ID或重用了一个预定义的常量。你可以通过调用wx.RegisterId()来确保在应用程序中wxPython不在别处使用你的ID。要防止你的程序使用相同的 wxPython ID,你应该避免使用全局常量wx.ID_LOWEST和wx.ID_HIGHEST之间的ID号。
使用全局性的NewID()函数
自己确保ID号的唯一性十分麻烦,你可以使用wx.NewId()函数让wxPython来为你创建ID:
id = wx.NewId()
frame = wx.Frame.__init__(None, id)
你也可以给窗口部件的构造器传递全局常量wx.ID_ANY或-1,然后wxPython将为你生成新的ID。然后你可以在需要这个ID时使用GetId()方法来得到它:
frame = wx.Frame.__init__(None, -1)
id = frame.GetId()
2.5.3 使用wx.Size和wx.Point
wx.Frame构造器的参数也引用了类wx.Size和wx.Point。这两个类在你的wxPython编程中将频繁被使用。
wx.Point类表示一个点或位置。构造器要求点的x和y值。如果不设置x,y值,则值默认为0。我们可以使用Set(x,y)和Get()函数来设置和得到x和y值。Get()函数返回一个元组。x和y值可以像下面这样作为属性被访问:
point = wx.Point(10, 12)
x = point.x
y = point.y
另外,wx.Point的实例可以像其它Python对象一样作加、减和比较运算,例如:
a = wx.Point(2, 3)
b = wx.Point(5, 7)
c = a + b
bigger = a > b
在wx.Point的实参中,坐标值一般为整数。如果你需要浮点数坐标,你可以使用类wx.RealPoint,它的用法如同wx.Point。
wx.Size类几乎和wx.Point完全相同,除了实参的名字是width和height。对wx.Size的操作与wx.Point一样。
在你的应用程序中当一个wx.Point或wx.Size实例被要求的时候(例如在另一个对象的构造器中),你不必显式地创建这个实例。你可以传递一个元组给构造器,wxPython将隐含地创建这个wx.Point或wx.Size实例:
frame = wx.Frame(None, -1, pos=(10, 10), size=(100, 100))
2.5.4 使用wx.Frame的样式
每个wxPython窗口部件都要求一个样式参数。这部分我们将讨论用于wx.Frame的样式。它们中的一些也适用于别的wxPython窗口部件。一些窗口部件也定义了一个SetStyle()方法,让你可以在该窗口部件创建后改变它的样式。所有的你能使用的样式元素都有一个常量标识符(如 wx.MINIMIZE_BOX)。要使用多个样式,你可以使用或运算符|。例如,wx.DEFAULT_FRAME_STYLE样式就被定义为如下几个基本样式的组合:
wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX | wx.RESIZE_BORDER |
wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX
要从一个合成的样式中去掉个别的样式,你可以使用^操作符。例如要创建一个默认样式的窗口,但要求用户不能缩放和改变窗口的尺寸,你可以这样做:
wx.DEFAULT_FRAME_STYLE ^ (wx.RESIZE_BORDER | wx.MINIMIZE_BOX |wx.MAXIMIZE_BOX)
如果你不慎使用了&操作符,那么将得到一个没有样式的、无边框图的、不能移动、不能改变尺寸和不能关闭的帧。
下表2.2列出了用于wx.Frame的最重要的样式:
wx.CAPTION:在框架上增加一个标题栏,它显示该框架的标题属性。
wx.CLOSE_BOX:指示系统在框架的标题栏上显示一个关闭框,使用系统默认的位置和样式。
wx.DEFAULT_FRAME_STYLE:默认样式。
wx.FRAME_SHAPED:用这个样式创建的框架可以使用SetShape()方法去创建一个非矩形的窗口。
wx.FRAME_TOOL_WINDOW:通过给框架一个比正常更小的标题栏,使框架看起来像一个工具框窗口。在Windows下,使用这个样式创建的框架不会出现在显示所有打开窗口的任务栏上。
wx.MAXIMIZE_BOX:指示系统在框架的标题栏上显示一个最大化框,使用系统默认的位置和样式。
wx.MINIMIZE_BOX:指示系统在框架的标题栏上显示一个最小化框,使用系统默认的位置和样式。
wx.RESIZE_BORDER:给框架增加一个可以改变尺寸的边框。
wx.SIMPLE_BORDER:没有装饰的边框。不能工作在所有平台上。
wx.SYSTEM_MENU:增加系统菜单(带有关闭、移动、改变尺寸等功能)和关闭框到这个窗口。在系统菜单中的改变尺寸和关闭功能的有效性依赖于wx.MAXIMIZE_BOX, wx.MINIMIZE_BOX和wx.CLOSE_BOX样式是否被应用。
下面的四张图显示了几个通常的框架的样式。
图2.4是使用wx.DEFAULT_STYLE创建的。
图2.5是使用wx.DEFAULT_FRAME_STYLE ^ (wx.RESIZE_BORDER | wx.MINIMIZE_BOX |wx.MAXIMIZE_BOX)组合样式创建的。
图2.6使用的样式是wx.DEFAULT_FRAME_STYLE | wx.FRAME_TOOL_WINDOW。
图2.7使用了扩展样式 wx.help.FRAME_EX_CONTEXTHELP。
2.6 如何为一个框架增加对象和子窗口?
我们已经说明了如何创建wx.Frame对象,但是创建后的是空的。本节我们将介绍在你的框架中插入对象与子窗口的基础,以便与用户交互。
2.6.1 给框架增加窗口部件
图2.8显示了一个定制的wx.Frame的子类,名为InsertFrame。当点击close按钮时,这个窗口将关闭且应用程序将退出。例2.3定义了子类InsertFrame。
例2.3
#!/usr/bin/env python
import wx
class InsertFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
panel = wx.Panel(self) #创建画板
button = wx.Button(panel, label="Close", pos=(125, 10),
size=(50, 50)) #将按钮添加到画板
#绑定按钮的单击事件
self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button)
#绑定窗口的关闭事件
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
def OnCloseMe(self, event):
self.Close(True)
def OnCloseWindow(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = InsertFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
类InsertFrame 的方法__init__创建了两子窗口。第一个是wx.Panel,它是其它窗口的容器,它自身也有一点功能。第二个是wx.Button,它是一个平常按钮。接下来,按钮的单击事件和窗口的关闭事件被绑定到了相应的函数,当事件发生时这相应的函数将被调用执行。
大多数情况下,你将创建一个与你的wx.Frame大小一样的wx.Panel实例以容纳你的框架上的所有的内容。这样做可以让定制的窗口内容与其他如工具栏和状态栏分开。 通过tab按钮,可以遍历wx.Panel中的元素,wx.Frame不能。
你不必像使用别的UI工具包那样,你不需要显式调用一个增加方法来向双亲中插入一个子窗口。在wxPython中,你只需在子窗口被创建时指定父窗口,这个子窗口就隐式地增加到父对象中了,例如例2.3所示。
你可能想知道在例2.3中,为什么wx.Button被创建时使用了明确的位置和尺寸,而wx.Panel没有。在wxPython中,如果只有一个子窗口的框架被创建,那么那个子窗口(例2.3中是wx.Panel)被自动重新调整尺寸去填满该框架的客户区域。这个自动调整尺寸将覆盖关于这个子窗口的任何位置和尺寸信息,尽管关于子窗口的信息已被指定,这些信息将被忽略。这个自动调整尺寸仅适用于框架内或对话框内的只有唯一元素的情况。这里按钮是 panel的元素,而不是框架的,所以要使用指定的尺寸和位置。如果没有为这个按钮指定尺寸和位置,它将使用默认的位置(panel的左上角)和基于按钮标签的长度的尺寸。
显式地指定所有子窗口的位置和尺寸是十分乏味的。更重要的是,当用户调整窗口大小的时候,这使得子窗口的位置和大小不能作相应调整。为了解决这两个问题,wxPython使用了称为sizers的对象来管理子窗口的复杂布局。
2.6.2 给帧增加菜单栏、工具栏和状态栏。
图2.9显示了一个有菜单栏、工具栏和状态栏的帧。
例2.4显示了__init__方法,它用这三个子窗口装饰了一个简单的窗口。
例2.4
#!/usr/bin/env python
import wx
import images
class ToolbarFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Toolbars', status
size=(300, 200))
panel = wx.Panel(self)
panel.SetBackgroundColour('White')
statusBar = self.CreateStatusBar() #1 创建状态栏
toolbar = self.CreateToolBar() #2 创建工具栏
toolbar.AddSimpleTool(wx.NewId(), images.getNewBitmap(),
"New", "Long help for 'New'") #3 给工具栏增加一个工具
toolbar.Realize() #4 准备显示工具栏
menuBar = wx.MenuBar() # 创建菜单栏
# 创建两个菜单
menu1 = wx.Menu()
menuBar.Append(menu1, "&File")
menu2 = wx.Menu()
#6 创建菜单的项目
menu2.Append(wx.NewId(), "&Copy", "Copy in status bar")
menu2.Append(wx.NewId(), "C&ut", "")
menu2.Append(wx.NewId(), "Paste", "")
menu2.AppendSeparator()
menu2.Append(wx.NewId(), "&Options...", "Display Options")
menuBar.Append(menu2, "&Edit") # 在菜单栏上附上菜单
self.SetMenuBar(menuBar) # 在框架上附上菜单栏
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = ToolbarFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
说明:
#1:这行创建了一个状态栏,它是类wx.StatusBar的实例。它被放置在框架的底部,宽度与框架相同,高度由操作系统决定。状态栏的目的是显示在应用程序中被各种事件所设置的文本。
#2:创建了一个wx.ToolBar的实例,它是命令按钮的容器。它被自动放置在框架的顶部
#3:有两种方法来为你工具栏增加工具,这行使用了参数较少的一种:AddSimpleTool()。参数分别是ID,位图,该工具的短的帮助提示文本,显示在状态栏中的该工具的长的帮助文本信息。(此刻不要考虑位图从哪儿来)
#4:Realize()方法告诉工具栏这些工具按钮应该被放置在哪儿。这是必须的。
#6:创建菜单的项目,其中参数分别代表ID,选项的文本,当鼠标位于其上时显示在状态栏的文本。
2.7 如何使用一般的对话框?
wxPython提供了一套丰富的预定义的对话框。这部分,我们将讨论三种用对话框得到来自用户的信息:
1、消息对话框
2、文本输入对话框
3、从一个列表中选择
在wxPython中有许多别的标准对话框,包括文件选择器、色彩选择器、进度对话框、打印设置和字体选择器。这些将在第9章介绍。
消息对话框
与用户通信最基本的机制是wx.MessageDialog,它是一个简单的提示框。wx.MessageDialog可用作一个简单的OK框或yes/no对话框。下面的片断显示了yes/no对话框:
dlg = wx.MessageDialog(None, 'Is this the coolest thing ever!',
'MessageDialog', wx.YES_NO | wx.ICON_QUESTION)
result = dlg.ShowModal()
dlg.Destroy()
显示结果如图2.10所示:
wx.MessageDialog参数如下:
wx.MessageDialog(parent, message,
caption="Message box",
style=wx.OK | wx.CANCEL,
pos=wx.DefaultPosition)
参数说明:
parent:对话框的父窗口,如果对话框是顶级的则为None。
message:显示在对话框中的字符串。
caption:显示在对话框标题栏上的字符串。
style:对话框中按钮的样式。
pos:对话框出现的位置。
ShowModal ()方法将对话框以模式框架的方式显示,这意味着在对话框关闭之前,应用程序中的别的窗口不能响应用户事件。ShowModal()方法的返回值是一个整数,对于wx.MessageDialog,返回值是下面常量之一: wx.ID_YES, wx.ID_NO, wx.ID_CANCEL, wx.ID_OK。
文本输入对话框
如果你想从用户那里得到单独一行文本,你可能使用类wx.TextEntryDialog。下面的片断创建了一个文本输入域,当用户单击OK按钮退出时,获得用户输入的值:
dlg = wx.TextEntryDialog(None, "Who is buried in Grant's tomb?",
'A Question', 'Cary Grant')
if dlg.ShowModal() == wx.ID_OK:
response = dlg.GetValue()
图2.11显示了上面这个对话框:
上面的wx.TextEntryDialog的参数按顺序说明是,父窗口,显示在窗口中的文本标签,窗口的标题(默认是 “Please enter text”),输入域中的默认值。同样它也有一个样式参数,默认是wx.OK | wx.CANCEL。与 wx.MessageDialog一样,ShowModal()方法返回所按下的按钮的ID。GetValue()方法得到用户输入在文本域中的值(这有一个相应的SetValue()方法让你可以改变文本域中的值)。
从一个列表中选择
你可以让用户只能从你所提供的列表中选择,你可以使用类wx.SingleChoiceDialog。下面是一个简单的用法:
dlg = wx.SingleChoiceDialog(None,
'What version of Python are you using?',
'Single Choice',
['1.5.2', '2.0', '2.1.3', '2.2', '2.3.1'],
if dlg.ShowModal() == wx.ID_OK:
response = dlg.GetStringSelection()
图2.12显示了上面代码片断的结果。
wx.SingleChoiceDialog的参数类似于文本输入对话框,只是以字符串的列表代替了默认的字符串文本。要得到所选择的结果有两种方法, GetSelection()方法返回用户选项的索引,而GetStringSelection()返回实际所选的字符串。
2.8 一些最常见的错误现象及解决方法?
有一些错误它们可能会发生在你的wxPython应用程序对象或初始的顶级窗口在创建时,这些错误可能是很难诊断的。下面我们列出一些最常见的错误现象及解决方法:
错误现象:
程序启动时提示“unable to import module wx。”
原因:
wxPython模块不在你的PYTHONPATH中。这意味着wxPython没有被正确地安装。如果你的系统上有多个版本的Python,wxPython可能安装在了你没有使用的Python版本中。
解决方法:
首先,确定你的系统上安装了哪些版本的Python。在Unix系统上,使用which python命令将告诉你默认的安装。在Windows系统上,如果wxPython被安装到了相应的Python版本中,它将位于<python-home>/Lib/site-packages子目录下。然后重装wxPython。
错误现象:
应用程序启动时立即崩溃,或崩溃后出现一空白窗口。
原因:
在wx.App创建之前,创建或使用了一个wxPython对象。
解决方法:
在启动你的脚本时立即创建wx.App对象。
错误现象:
顶级窗口被创建同时又立刻关闭。应用程序立即退出。
原因:
没有调用wx.App的MainLoop()方法。
解决方法:
在你的所有设置完成后调用MainLoop()方法。
错误现象:
顶级窗口被创建同时又立刻关闭。应用程序立即退出。但我调用了MainLoop()方法。
原因:
你的应用程序的OnInit()方法中有错误,或OnInit()方法调用了某些方法(如帧的__init__()方法)。
解决方法:
在MainLoop()被调用之前出现错误的话,这将触发一个异常且程序退出。如果你的应用程序设置了重定向输出到窗口,那么那些窗口将一闪而过,你不能看到显示在窗口中的错误信息。这种情况下,你要使用 redirect=False关闭重定向选项,以便看到错误提示。
2.9 总结
1、wxPython程序的实现基于两个必要的对象:应用程序对象和顶级窗口。任何wxPython应用程序都需要去实例化一个wx.App,并且至少有一个顶级窗口。
2、应用程序对象包含OnInit()方法,它在启动时被调用。在这个方法中,通常要初始化框架和别的全局对象。wxPython应用程序通常在它的所有的顶级窗口被关闭或主事件循环退出时结束。
3、应用程序对象也控制wxPython文本输出的位置。默认情况下,wxPython重定向stdout和stderr到一个特定的窗口。这个行为使得诊断启动时产生的错误变得困难了。但是我们可以通过让wxPython把错误消息发送到一个文件或控制台窗口来解决。
4、一个wxPython应用程序通常至少有一个wx.Frame的子类。一个wx.Frame对象可以使用style参数来创建组合的样式。每个 wxWidget对象,包括框架,都有一个ID,这个ID可以被应用程序显式地赋值或由wxPython生成。子窗口是框架的内容,框架是它的双亲。通常,一个框架包含一个单一的wx.Panel,更多的子窗口被放置在这个Panel中。框架的唯一的子窗口的尺寸自动随其父框架的尺寸的改变而改变。框架有明确的关于管理菜单栏、工具栏和状态栏的机制。
5、尽管你将使用框架做任何复杂的事情,但当你想简单而快速地得到来自用户的信息时,你可以给用户显示一个标准的对话窗口。对于很多任务都有标准的对话框,包括警告框、简单的文本输入框和列表选择框等等。
昵称: 和尚娶媳妇 时间: 2007-06-15 16:27:00
3、在事件驱动环境中工作
事件处理是wxPython程序工作的基本机制。主要执行事件处理的工作称为事件驱动。在这章中我们将讨论什么是事件驱动应用程序,它与传统的应用程序有什么不同。我们将对在GUI编程中所使用的概念和术语提供一些介绍,包括与用户交互,工具包和编程逻辑。也将包括典型事件驱动程序的生命周期。
事件就是发生在你的系统中的事,你的应用程序通过触发相应的功能以响应它。事件可以是低级的用户动作,如鼠标移动或按键按下,也可以是高级的用户动作(定义在wxPython的窗口部件中的),如单击按钮或菜单选择。事件可以产生自系统,如关机。你甚至可以创建你自己的对象去产生你自己的事件。wxPython应用程序通过将特定类型的事件和特定的一块代码相关联来工作,该代码在响应事件时执行。事件被映射到代码的过程称为事件处理。
本章将说明事件是什么,你如何写响应一个事件的代码,以及wxPython在事件发生的时候是如何知道去调用你的代码的。我们也将说明如何将定制的事件增加到wxPython库中,该库包含了关于用户和系统行为的标准事件的一个列表。
的但未处理的事件的一个列表。
事件处理器(event handler):响应事件时所调用的函数或方法。也称作处理器函数或处理器方法。
事件绑定器(event binder):一个封装了特定窗口部件,特定事件类型和一个事件处理器的wxPython对象。为了被调用,所有事件处理器必须用一个事件绑定器注册。
wx.EvtHandler:一个wxPython类,它允许它的实例在一个特定类型,一个事件源,和一个事件处理器之间创建绑定。注意,这个类与先前定义的事件处理函数或方法不是同一个东西。
3.2 什么是事件驱动编程?
事件驱动程序主要是一个控制结构,它接受事件并响应它们。wxPython程序(或任何事件驱动程序)的结构与平常的Python脚本不同。标准的Python脚本有一个特定的开始点和结束点,程序员使用条件、循环、和函数来控制执行顺序。
从用户的角度上来看,wxPython程序大部分时间什么也不做,一直闲着直到用户或系统做了些什么来触发这个wxPython程序动作。wxPython 程序的结构就是一个事件驱动程序体系的例子。图3.1是事件处理循环的示意,它展示了主程序的生命、用户事件、和分派到的处理器函数。
事件驱动系统的主循环类似于客户服务呼叫中心的操作者。当没有呼叫的进入的时候,这个操作者处于等待状态。当一个事件发生的时候,如电话铃响了,这个操作者开始一个响应过程,他与客户交谈直到他获得足够的信息以分派该客户给一个合适的回答者。然后操作者等待下一个事件。
尽管每个事件驱动系统之间有一些不同,但它们有很多相似的地方。下面列出了事件驱动程序结构的主要特点:
1、在初始化设置之后,程序的大部分时间花在了一个空闭的循环之中。进入这个循环就标志着程序与用户交互的部分的开始,退出这个循环就标志结束。在 wxPython中,这个循环的方法是:wx.App.MainLoop(),并且在你的脚本中显式地被调用。当所有的顶级窗口关闭时,主循环退出。
2、程序包含了对应于发生在程序环境中的事情的事件。事件通常由用户的行为触发,但是也可以由系统的行为或程序中其他任意的代码。在wxPython中,所有的事件都是类wx.Event或其子类的一个实例。每个事件都有一个事件类型属性,它使得不同的事件能够被辨别。例如,鼠标释放和鼠示按下事件都被认为是同一个类的实例,但有不同的事件类型。
3、作为这个空闭的循环部分,程序定期检查是否有任何请求响应事情发生。有两种机制使得事件驱动系统可以得到有关事件的通知。最常被wxPython使用的方法是,把事件传送到一个中心队列,由该队列触发相应事件的处理。另一种方法是使用轮询的方法,所有可能引发事件的事件主被主过程定期查询并询问是否有没有处理的事件。
4、当事件发生时,基于事件的系统试着确定相关代码来处理该事件,如果有,相关代码被执行。在wxPython中,原系统事件被转换为wx.Event实例,然后使用 wx.EvtHandler.ProcessEvent()方法将事件分派给适当的处理器代码。图3.3呈现了这个过程:
事件机制的组成部分是事件绑定器对象和事件处理器。事件绑定器是一个预定义的wxPython对象。每个事件都有各自的事件绑定器。事件处理器是一个函数或方法,它要求一个wxPython事件实例作为参数。当用户触发了适当的事件时,一个事件处理器被调用。
下面我们将讨论有关wxPython更多的细节,我们把事件响应的基本单元“事件处理器”作为开始。
3.2.1 编写事件处理器
在你的wxPython代码中,事件和事件处理器是基于相关的窗口部件的。例如,一个按钮被单击被分派给一个基于该按钮的专用的事件处理器。为了要把一个来自特定窗口部件的事件绑定到一个特定的处理器方法,你要使用一个绑定器对象来管理这个连接。例如:
self.Bind(wx.EVT_BUTTON, self.OnClick, aButton)
上例使用了预定义的事件绑定器对象wx.EVT_BUTTON来将aButton对象上的按钮单击事件与方法self.OnClick相关联起来。这个 Bind()方法是wx.EvtHandler的一个方法,wx.EvtHandler是所有可显示对象的父类。因此上例代码行可以被放置在任何显示类。
即使你的wxPython程序表面上看起来在被动地等待事件,但它仍在做事。它在运行方法wx.App.MainLoop(),该方法是一个无限的循环。MainLoop()方法可以使用Python伪代码表示如下:
while True:
while not self.Pending():
self.ProcessIdle()
self.DoMessage()
上面的伪代码意思是如果没有未处理的消息,则做一些空闲时做的事;如果有消息进入,那么将这个消息分派给适当的事件处理方法。
3.2.2 设计事件驱动程序
对于事件驱动程序的设计,由于没有假设事件何时发生,所以程序员将大量的控制交给了用户。你的wxPython程序中的大多数代码通过用户或系统的行为被直接或间接地执行。例如在用户选择了一个菜单项、或按下一个工具栏按钮、或按下了特定的按键组合后,你的程序中有关保存工作的代码被执行了。
另一方面,事件驱动体系通常是分散性的。响应一个窗口部件事件的代码通常不是定义在该部件的定义中的。例如,响应一个按钮单击事件的代码不必是该按钮定义的一部分,而可以存在在该按钮所附的框架中或其它地方。当与面向对象设计结合时,这个体系导致了松散和高度可重用的代码。你将会发现Python的灵活使得重用不同的wxPython应用程序的通常的事件处理器和结构变得非常容易。
3.2.3 事件触发
在wxPython 中,大部分窗口部件在响应低级事件时都导致高级事件发生。例如,在一个wx.Button上的鼠标单击导致一个EVT_BUTTON事件的生成,该事件是 wx.CommandEvent的特定类型。类似的,在一个窗口的角中拖动鼠标将导致wxPython为你自动创建一个wx.SizeEvent事件。高级事件的用处是让你的系统的其它部分更容易聚焦于最有关联的事件上,而不是陷于追踪每个鼠标单击。高级事件能够封装更多关于事件的有用的信息。当你创建你自已的定制的窗口部件时,你能定义你自己的定制事件以便管理事件的处理。
在wxPython中,代表事件的是事件对象。事件对象是类wx.Event或其子类的一个实例。父类wx.Event相对小且抽象,它只是包含了对所有事件的一些通常的信息。wx.Event的各个子类都添加了更多的信息。
在wxPython中,有一些wx.Event的子类。表3.2包含了你将最常遇到的一些事件类。记住,一个事件类可以有多个事件类型,每个都对应于一个不同的用户行为。下表3.2是wx.Event的重要的子类。
wx.CloseEvent:当一个框架关闭时触发。这个事件的类型分为一个通常的框架关闭和一个系统关闭事件。
wx.CommandEvent:与窗口部件的简单的各种交互都将触发这个事件,如按钮单击、菜单项选择、单选按钮选择。这些交互有它各自的事件类型。许多更复杂的窗口部件,如列表等则定义wx.CommandEvent的子类。事件处理系统对待命令事件与其它事件不同。
wx.KeyEvent:按按键事件。这个事件的类型分按下按键、释放按键、整个按键动作。
wx.MouseEvent:鼠标事件。这个事件的类型分鼠标移动和鼠标敲击。对于哪个鼠标按钮被敲击和是单击还是双击都有各自的事件类型。
wx.PaintEvent:当窗口的内容需要被重画时触发。
wx.SizeEvent:当窗口的大小或其布局改变时触发。
wx.TimerEvent:可以由类wx.Timer类创建,它是定期的事件。
通常,事件对象需要使用事件绑定器和事件处理系统将它们传递给相关的事件处理器。
3.3 如何将事件绑定到处理器?
事件绑定器由类wx.PyEventBinder的实例组成。一个预定义的wx.PyEventBinder的实例被提供给所有支持的事件类型,并且在你需要的时候你可以为你定制的事件创建你自己的事件绑定器。每个事件类型都有一个事件绑定器,这意味着一个wx.Event的子类对应多个绑定器。
在wxPython中,事件绑定器实例的名字是全局性的。为了清楚地将事件类型与处理器联系起来,它们的名字都是以wx.EVT_开头并且对应于使用在C++ wxWidgets代码中宏的名字。值得强调的是,wx.EVT绑定器名字的值不是你通过调用一个wx.Event实例的GetEventType()方法得到的事件类型的实际的整数码。事件类型整数码有一套完全不同的全局名,并且在实际中不常被使用。
作为wx.EVT名字的例子,让我们看看wx.MouseEvent的事件类型。正如我们所提到的,它们有十四个,其中的九个涉及到了基于在按钮上的敲击,如鼠标按下、鼠标释放、或双击事件。这九个事件类型使用了下面的名字:
wx.EVT_LEFT_DOWN
wx.EVT_LEFT_UP
wx.EVT_LEFT_DCLICK
wx.EVT_MIDDLE_DOWN
wx.EVT_MIDDLE_UP
wx.EVT_MIDDLE_DCLICK
wx.EVT_RIGHT_DOWN
wx.EVT_RIGHT_UP
wx.EVT_RIGHT_DCLICK
另外,类型wx.EVT_MOTION产生于用户移动鼠标。类型wx.ENTER_WINDOW和wx.LEAVE_WINDOW产生于当鼠标进入或离开一个窗口部件时。类型wx.EVT_MOUSEWHEEL被绑定到鼠标滚轮的活动。最后,你可以使用类型wx.EVT_MOUSE_EVENTS一次绑定所有的鼠标事件到一个函数。
同样,类wx.CommandEvent有28个不同的事件类型与之关联;尽管有几个仅针对老的Windows 操作系统。它们中的大多数是专门针对单一窗口部件的,如wx.EVT_BUTTON用于按钮敲击,wx.EVT_MENU用于菜单项选择。用于专门窗口部件的命令事件在part2中讨论。
绑定机制的好处是它使得wxPython可以很细化地分派事件,而仍然允许同类的类似事件发生并且共享数据和功能。这使得在wxPython中写事件处理比在其它界面工具包中清细得多。
事件绑定器被用于将一个wxPython窗口部件与一个事件对象和一个处理器函数连接起来。这个连接使得wxPython系统能够通过执行处理器函数中的代码来响应相应窗口部件上的事件。在wxPython中,任何能够响应事件的对象都是wx.EvtHandler的子类。所有窗口对象都是 wx.EvtHandler的子类,因些在wxPython应用程序中的每个窗口部件都能够响应事件。类wx.EvtHandler也能够被非窗口部件对象所使用,如wx.App,因此事件处理功能不是限于可显示的窗口部件。我们所说的窗口部件能响应事件的意思是:该窗口部件能够创建事件绑定,在分派期间 wxPython能够识别该事件绑定。由绑定器调用的在事件处理器函数中的实际代码不是必须位于一个wx.EvtHandler类中。
3.3.1 使用wx.EvtHandler的方法工作
wx.EvtHandler类定义的一些方法在一般情况下用不到。你会经常使用的wx.EvtHandler的方法是Bind(),它创建事件绑定。该方法的用法如下:
Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)
Bind ()函数将一个事件和一个对象与一个事件处理器函数关联起来。参数event是必选的,它是我们在3.3节中所说的wx.PyEventBinder的一个实例。参数handler也是必选的,它是一个可调用的Python对象,通常是一个被绑定的方法或函数。处理器必须是可使用一个参数(事件对象本身)来调用的。参数handler可以是None,这种情况下,事件没有关联的处理器。参数source是产生该事件的源窗口部件,这个参数在触发事件的窗口部件与用作事件处理器的窗口部件不相同时使用。通常情况下这个参数使用默认值None,这是因为你一般使用一个定制的wx.Frame类作为处理器,并且绑定来自于包含在该框架内的窗口部件的事件。父窗口的__init__是一个用于声明事件绑定的方便的位置。但是如果父窗口包含了多个按钮敲击事件源(比如OK按钮和Cancel按钮),那么就要指定source参数以便wxPython区分它们。下面是该方法的一个例子:
self.Bind(wx.EVT_BUTTON, self.OnClick, button)
下例3.1演示了使用参数source和不使用参数source的方法,它改编自第二章中的代码:
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
panel = wx.Panel(self, -1)
button = wx.Button(panel, -1, "Close", pos=(130, 15),
size=(40, 40))
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) #1 绑定框架关闭事件
self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) #2 绑定按钮事件
def OnCloseMe(self, event):
self.Close(True)
def OnCloseWindow(self, event):
self.Destroy()
说明:
#1 这行绑定框架关闭事件到self.OnCloseWindow方法。由于这个事件通过该框架触发且用于帧,所以不需要传递一个source参数。
#2 这行将来自按钮对象的按钮敲击事件绑定到self.OnCloseMe方法。这样做是为了让wxPython能够区分在这个框架中该按钮和其它按钮所产生的事件。
你也可以使用source参数来标识项目,即使该项目不是事件的源。例如,你可以绑定一个菜单事件到事件处理器,即使这个菜单事件严格地说是由框架所触发的。下例3.2演示了绑定一个菜单事件的例子:
#!/usr/bin/env python
import wx
class MenuEventFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Menus',
size=(300, 200))
menuBar = wx.MenuBar()
menu1 = wx.Menu()
menuItem = menu1.Append(-1, "&Exit...")
menuBar.Append(menu1, "&File")
self.SetMenuBar(menuBar)
self.Bind(wx.EVT_MENU, self.OnCloseMe, menuItem)
def OnCloseMe(self, event):
self.Close(True)
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MenuEventFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
Bind ()方法中的参数id和id2使用ID号指定了事件的源。一般情况下这没必要,因为事件源的ID号可以从参数source中提取。但是某些时候直接使用 ID是合理的。例如,如果你在使用一个对话框的ID号,这比使用窗口部件更容易。如果你同时使用了参数id和id2,你就能够以窗口部件的ID号形式将这两个ID号之间范围的窗口部件绑定到事件。这仅适用于窗口部件的ID号是连续的。
注意:Bind()方法出现在wx.Python2.5中,以前版本的事件绑定中,EVT_*的用法如同函数对象,因此你会看到如下的绑定调用:
wx.EVT_BUTTON(self, self.button.GetId(), self.OnClick)
这个方式的缺点是它不像是面向对象的方法调用。然而,这个老的样式仍可工作在2.5的版本中(因为wx.EVT*对象仍是可调用的)。
下表3.3列出了最常使用的wx.EvtHandler的方法:
AddPendingEvent(event):将这个event参数放入事件处理系统中。类似于ProcessEvent(),但它实际上不会立即触发事件的处理。相反,该事件被增加到事件队列中。适用于线程间的基于事件的通信。
Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY):完整的说明见3.3.1节。
GetEvtHandlerEnabled()
SetEvtHandlerEnabled( boolean):如果处理器当前正在处理事件,则属性为True,否则为False。
ProcessEvent(event):把event对象放入事件处理系统中以便立即处理。
3.4 wxPython是如何处理事件的?
基于事件系统的关键组成部分是事件处理。通过它,一个事件被分派到了相应的用于相应该事件的一块代码。在这一节,我们将讨论wxPython处理事件的过程。我们将使用小段的代码来跟踪这个处理的步骤。图 3.2显示了一个带有一个按钮的简单窗口,这个按钮将被用来产生一个简单的事件。
下例3.3包含了生成这个窗口的代码。在这个代码中,通过敲击按钮和将鼠标移动到按钮上都可产生wxPython事件。
例3.3绑定多个鼠标事件
#!/usr/bin/env python
import wx
class MouseEventFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
self.panel = wx.Panel(self)
self.button = wx.Button(self.panel,
label="Not Over", pos=(100, 15))
self.Bind(wx.EVT_BUTTON, self.OnButtonClick,
self.button) #1 绑定按钮事件
self.button.Bind(wx.EVT_ENTER_WINDOW,
self.OnEnterWindow) #2 绑定鼠标位于其上事件
self.button.Bind(wx.EVT_LEAVE_WINDOW,
self.OnLeaveWindow) #3 绑定鼠标离开事件
def OnButtonClick(self, event):
self.panel.SetBackgroundColour('Green')
self.panel.Refresh()
def OnEnterWindow(self, event):
self.button.SetLabel("Over Me!")
event.Skip()
def OnLeaveWindow(self, event):
self.button.SetLabel("Not Over")
event.Skip()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MouseEventFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
说明:
MouseEventFrame包含了一个位于中间的按钮。在其上敲击鼠标将导致框架的背景色改变为绿色。#1绑定了鼠标敲击事件。当鼠标指针位于这个按钮上时,按钮上的标签将改变,这用#2绑定。当鼠标离开这个按钮时,标签变回原样,这用#3绑定。
通过观察上面的鼠标事件例子,我们引出了在wxPython中的事件处理的一些问题。#1中,按钮事件由附着在框架上的按钮触发,那么wxPython怎么知道在框架对象中查找绑定而不是在按钮对象上呢?在#2和#3中,鼠标的进入和离开事件被绑定到了按钮,为什么这两个事件不能被绑到框架上呢。这些问题将通过检查wxPython用来决定如何响应事件的过程来得到回答。
3.4.1 理解事件处理过程
wxPython的事件处理过程被设计来简化程序员关于事件绑定的创建,使他们不必考虑哪些不重要的事件。
隐藏在简化设计之下的底层机制是有些复杂的。接下来,我们将跟踪关于按钮敲击和鼠标进入事件的过程。
图3.3显示了事件处理过程的一个基本的流程。矩形代表过程的开始和结束,环形代表各种wxPython对象(它们是这个过程的一部分),棱形代表判断点,带条的矩形代表实际的事件处理方法。
事件处理过程开始于触发事件的对象。通常,wxPython首先在触发对象中查找匹配事件类型的被绑定的处理器函数。如果找到,则相应的方法被执行。否则,wxPython将检查该事件是否传送到了上一级的容器。如果是的话,父窗口部件将被检查,这样一级一级向上寻找,直到wxPython找到了一个处理器函数或到达了顶级窗口。如果事件没有传播,在处理过程完成之前,wxPython仍将为了处理器函数而检查应用程序对象。
当事件处理器运行时,过程通常就结束了。然而,函数可以告诉wxPython去继续查找处理器。
下面让我们仔细观察一下这个过程的每一个步骤。我们的每步分析都有图3.3的一个相关略图。
第一步,创建事件
这个过程开始于事件被创建时。
在wxPython架构中已经创建了大多数的事件类型,它们用于响应特定的用户动作或系统通知。例如,当wxPython通知“鼠标移进了一个新窗口部件对象时”,鼠标进入事件被触发,鼠标敲击事件在鼠标按下或释放后被创建。
事件首先被交给创建事件的对象。对于按钮敲击,这个对象是按钮;对于鼠标进入事件,这个对象是所进入的窗口部件。
第二步,确定事件对象是否被允许处理事件。
事件处理过程检查的下一步是看相关窗口部件当前是否被允许去处理事件。
通过调用wx.EvtHandler的SetEvtHandlerEnabled(boolean)方法,一个窗口可以被设置为允许或不允许事件处理。不允许事件处理的结果是该窗口部件在事件处理中被完全绕过,与该对象关联的绑定对象也不会被搜索,并且在这步中的处理没有向下的分支。
在事件处理器级使一个窗口部件有效或无效与在用户界面级(UI)不一样。在UI级使一个窗口部件无效或有效,使用wx.Window的方法Disable()和 Enable()。在UI级使一个窗口部件无效意味用户不能与这个无效的窗口部件交互。通常无效的窗口部件在屏幕上以灰化的状态表示。一个在UI级无效的窗口不能产生任何事件;但是,如果它对于别的事件是容器的级别,它仍然能够处理它接受到的事件。本节的剩余内容,我们将在wx.EvtHandler层面上使用有效和无效,这涉及到窗口部件是否被允许处理事件。
第三步 定位绑定器对象
如图3.6所示
然后ProcessEvent()方法寻找一个绑定器对象,该绑定器对象确定当前对象和事件类型之间的绑定。
如果对象自身的绑定器没有被找到,那么向上到该对象的超类中去寻找。如果一个绑定器对象被发现,wxPython调用相关的处理器函数。在处理器被调用后,该事件的事件处理停止,除非处理器函数显式地要求作更多的处理。
在例子3.3中,因为在按钮对象,绑定器对象wx.EVT_ENTER_WINDOW,和相关的方法OnEnterWindow()之间定义了绑定,所以鼠标进入事件被捕获,OnEnterWindow()方法被调用。由于我们没有绑定鼠标敲击事件
wx.EVT_LEFT_DOWN,在这种情况下,wxPython将继续搜索。
第四步 决定是否继续处理
如图3.7所示
在调用了第一个事件处理器之后,wxPython查看是否有进一步的处理要求。事件处理器通过调用wx.Event的方法Skip()要求更多的处理。如果Skip()方法被调用,那么处理将继续,并且任何定义在超类中的处理器在这一步中被发现并执行。Skip()方法在处理中的任一点或处理器所调用的任何代码中都可以被调用。Skip()方法在事件实例中设置一个标记,在事件处理器方法完成后,wxPython检查这个标记。在例3.3中, OnButtonClick()不调用Skip(),因此在那种情况下,处理器方法结束后,事件处理完成。在另两个事件处理器中调用了Skip(),所以系统将保持搜索“匹配事件绑定”,最后对于原窗口部件的鼠标进入和离开事件调用默认的功能,如鼠标位于其上的事件。
第五步 决定是否展开
如图3.8所示
最后,wxPython决定是否将事件处理向上展开到容器级以发现一个事件处理器。所谓的容器级是从一个特定的窗口部件到顶层框架的路径,这个路径是从窗口部件到它的父容器,一直向上沿升。
如果当前对象没有关于该事件的一个处理器,或如果处理器调用了Skip(),wxPython将决定是否这个事件将沿容器级向上展开。如果决定不,那么在 wx.App实例中再找寻一次处理器,然后停止。如果决定是,则事件处沿该窗口的容器级向上搜索,直到发现适当的绑定,或到达顶层框架对象,或到达一个 wx.Dialog对象(即使这个对话框不是顶级的)。如果ProcessEvent()返回True,事件则被认为发现了一个适当的绑定,这表示处理完成。到达一个wx.Dialog停止的目的是防止父框架被来自对话框的无关的或未预期的假事件干扰。
一个事件是否向上展开至容器级,这是每个事件实例的一个动态属性,尽管实际上默认值几乎总是使用那几个。默认情况,只有wx.CommandEvent及其子类的实例向上展开至容器级。其它的所有事件不这样做。
在例3.3中,按钮敲击事件得到处理。在wx.Button上敲击鼠标产生一个命令类型的事件wx.EVT_BUTTON。由于wx.EVT_BUTTON 属于一个wx.CommandEvent,所以wxPython在这个按钮对象中找寻绑定失败后,它将向上展开至容器级,先是按钮的父窗口panel。由于panel中没有相匹配的绑定,所以又向上至panel的父窗口frame。由于frame中有匹配的绑定,所以ProcessEvent()调用相关函数 OnButtonClick()。
第五步同时也说明了为什么鼠标进入和离开事件必须被绑定到按钮而不是框架。由于鼠标事件不是 wx.CommandEvent的子类,所以鼠标进入和离开事件不向上展开至容器级。如果鼠标进入和离开事件被绑定到了框架,那么当鼠标进入或离开框架时,wxPython触发鼠标进入或离开事件。
在这种方式中,命令事件是被优先对待的。因为它们被认为是高级事件,表示用户正在应用程序空间中做一些事,而非窗口系统。窗口系统类型事件只对窗口部件感兴趣,而应用级事件对容器级。这个规则不防碍我们在任何地方声明绑定,不管被绑定的是什么对象或什么对象定义事件处理器。例如,即使这个绑定的鼠标敲击事件针对于按钮对象,而绑定则被定义在这个框架类中,且调用这个框架内的方法。换句话说,低级的非命令事件通常用于窗口部件或一些系统级的通知,如鼠标敲击、按键按下、绘画请求、调整大小或移动。另一方面,命令事件,如在按钮上敲击鼠标、或列表框上的选择,通常由窗口部件自己生成。例如,在适当的窗口部件上按下和释放鼠标后,按钮命令事件产生。
最后,如果遍历了容器级后,事件没有被处理,那么应用程序的wx.App对象调用ProcessEvent()。默认情况下,这什么也不做,但是你可以给你的wx.App增加事件绑定,以便以非标准的方式来传递事件。例如,假如你在写一个GUI构建器,你可能想把你构建器窗口中的事件传到你的代码窗口中,即使它们都是顶级窗口。方法之一是捕获应用程序对象中的事件,并把它们传递到代码窗口上。
3.4.2 使用Skip()方法
事件的第一个处理器函数被发现并执行完后,该事件处理将终止,除非在处理器返回之前调用了该事件的Skip()方法。调用Skip()方法允许另外被绑定的处理器被搜索,搜索依据3.4.1节中的第四步中声明的规则,因此父类和父窗口被搜索,就如同这第一个处理器不存在一样。在某些情况下,你想继续处理事件,以便原窗口部件的默认行为和你定制的处理能被执行。例3.4显示了一个使用Skip()的例子,它使得程序能够同时响应同一按钮上的鼠标左按键按下和按钮敲击。
例3.4 同时响应鼠标按下和按钮敲击
#!/usr/bin/env python
import wx
class DoubleEventFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
self.panel = wx.Panel(self, -1)
self.button = wx.Button(self.panel, -1, "Click Me", pos=(100, 15))
self.Bind(wx.EVT_BUTTON, self.OnButtonClick,
self.button) #1 绑定按钮敲击事件
self.button.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) #2 绑定鼠标左键按下事件
def OnButtonClick(self, event):
self.panel.SetBackgroundColour('Green')
self.panel.Refresh()
def OnMouseDown(self, event):
self.button.SetLabel("Again!")
event.Skip() #3 确保继续处理
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = DoubleEventFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
#1 这行绑定按钮敲击事件到OnButtonClick()处理器,这个处理器改变框架的背景色。
#2 这行绑定鼠标左键按下事件到OnMouseDown()处理器,这个处理器改变按钮的标签文本。由于鼠标左键按下事件不是命令事件,所以它必须被绑定到按钮(self.button.Bind)而非框架(self.Bind)。
当用户在按钮上敲击鼠标时,通过直接与底层操作系统交互,鼠标左键按下事件首先被产生。通常情况下,鼠标左键按下事件改变按钮的状态,随着鼠标左键的释放,产生了wx.EVT_BUTTON敲击事件。由于行#3的Skip()语句,DoubleEventFrame维持处理。没有Skip()语句,事件处理规则发现在#2创建的绑定,而在按钮能产生wx.EVT_BUTTON事件之前停止。由于Skip()的调用,事件处理照常继续,并且按钮敲击被创建。
记住,当绑定低级事件时如鼠标按下或释放,wxPython期望捕获这些低级事件以便生成进一步的事件,为了进一步的事件处理,你必须调用Skip()方法,否则进一步的事件处理将被阻止。
对于初始对象有效或无效状态的检查,这发生在ProcessEvent()方法中,该方法由wxPython系统调用以开始和处理事件分配机制。我们将在事件处理过程中一再看到ProcessEvent()方法,它是类 wx.EvtHandler中的方法,它实际上执行图3.3所描绘的大量事件处理。如果ProcessEvent()方法最后完成了事件处理,则 ProcessEvent()返回True。如果一个处理器被发现和组合事件被处理,则认为处理完成。处理器函数可以通过调用wx.Event的Skip ()方法来显式地请求进一步的处理。另处,如果初始对象是wx.Window的一个子类,那么它能够使用一个称为validator的对象来过滤事件。 Validator将在第九章中详细讨论。
3.5 在应用程序对象中还包含哪些其它的属性?
要更直接地管理主事件循环,你可以使用一些 wx.App方法来修改它。例如,按你的计划,你可能想开始处理下一个有效的事件,而非等待wxPython去开始处理。如果你正在执行一个长时间的过程,并且不想图形界面被冻结,那么这个特性是必要的,通常你不需要使用这节中的这些方法,但是,这些性能有时是很重要的。
下表3.4列出了你可以用来修改主循环的wx.App方法:
Dispatch():迫使事件队列中的下一个事件被发送。通过MainLoop()使用或使用在定制的事件循环中。
Pending():如果在wxPython应用程序事件队列中有等待被处理的事件,则返回True。
Yield(onlyIfNeeded=False):允许等候处理的wxWidgets事件在一个长时间的处理期间被分派,否则窗口系统将被锁定而不能显示或更新。如果等候处理的事件被处理了,则返回True,否则返回False。
onlyIfNeeded参数如果为True,那么当前的处理将让位于等候处理的事件。如果该参数为False,那么递归调用Yield是错误的。
这里也有一个全局函数wx.SafeYield(),它阻止用户在Yield期间输入数据(这通过临时使用来输入的窗口部件无效来达到目的),以免干扰Yield任务。
另一管理事件的方法是通过定制的方式,它创建你自己的事件类型,以匹配你的应用程序中特定的数据和窗口部件。下一节我们将讨论如何创建你自己的定制事件。
3.6 如何创建自己的事件?
尽管这是一个更高级的主题,但是我们将在这里讨论定制事件。当你第一次阅读的时候,你可以跳过并且以后再回过头来读。
为了要与wxPython提供的事件类相区别,你可以创建你自己定制的事件。你可以定制事件以响应哪些针对你的应用程序的数据更新或其它改变,此处定制的事件必须负责你的自定义数据。创建定制的事件类的另一个原因是:你可以针对所定制的窗口部件,使用它自己独特的命令事件类型。下一节中,我们将看一个定制窗口部件的例子。
3.6.1 为一个定制的窗口部件定义一个定制的事件。
图3.9显示了这个窗口部件,一个画板(panel)包含了两个按钮。自定义的事件TwoButtonEvent仅当用户敲击了这两个按钮之后被触发。这个事件包含了一个关于用户在该部件上敲击次数的计数。
创建自定义事件的步骤:
1、定义一个新的事件类,它是wxPython的wx.PyEvent类的子类。如果你想这个事件被作为命令事件,你可以创建 wx.PyCommandEvent的子类。像许多wxPython中的覆盖一样,一个类的py版本使得wxWidget系统明白用Python写的覆盖 C++方法的方法。
2、创建一个事件类型和一个绑定器对象去绑定该事件到特定的对象。
3、添加能够建造这个新事件实例的代码,并且使用ProcessEvent()方法将这个实例引入事件处理系统。一旦该事件被创建,你就可以像使用其它的wxPython事件一样创建绑定和处理器方法。
下例3.5显示了管理窗口部件的代码:
import wx
class TwoButtonEvent(wx.PyCommandEvent): #1 定义事件
def __init__(self, evtType, id):
wx.PyCommandEvent.__init__(self, evtType, id)
self.clickCount = 0
def GetClickCount(self):
return self.clickCount
def SetClickCount(self, count):
self.clickCount = count
myEVT_TWO_BUTTON = wx.NewEventType() #2 创建一个事件类型
EVT_TWO_BUTTON = wx.PyEventBinder(myEVT_TWO_BUTTON, 1) #3 创建一个绑定器对象
class TwoButtonPanel(wx.Panel):
def __init__(self, parent, id=-1, leftText="Left",
rightText="Right"):
wx.Panel.__init__(self, parent, id)
self.leftButton = wx.Button(self, label=leftText)
self.rightButton = wx.Button(self, label=rightText,
pos=(100,0))
self.leftClick = False
self.rightClick = False
self.clickCount = 0
#4 下面两行绑定更低级的事件
self.leftButton.Bind(wx.EVT_LEFT_DOWN, self.OnLeftClick)
self.rightButton.Bind(wx.EVT_LEFT_DOWN, self.OnRightClick)
def OnLeftClick(self, event):
self.leftClick = True
self.OnClick()
event.Skip() #5 继续处理
def OnRightClick(self, event):
self.rightClick = True
self.OnClick()
event.Skip() #6 继续处理
def OnClick(self):
self.clickCount += 1
if self.leftClick and self.rightClick:
self.leftClick = False
self.rightClick = False
evt = TwoButtonEvent(myEVT_TWO_BUTTON, self.GetId()) #7 创建自定义事件
evt.SetClickCount(self.clickCount) # 添加数据到事件
self.GetEventHandler().ProcessEvent(evt) #8 处理事件
class CustomEventFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Click Count: 0',
size=(300, 100))
panel = TwoButtonPanel(self)
self.Bind(EVT_TWO_BUTTON, self.OnTwoClick, panel) #9 绑定自定义事件
def OnTwoClick(self, event): #10 定义一个事件处理器函数
self.SetTitle("Click Count: %s" % event.GetClickCount())
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = CustomEventFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
说明:
#1 这个关于事件类的构造器声明为wx.PyCommandEvent的一个子类。 wx.PyEvent和wx.PyCommandEvent是 wxPython特定的结构,你可以用来创建新的事件类并且可以把C++类和你的Python代码连接起来。如果你试图直接使用wx.Event,那么在事件处理期间wxPython不能明白你的子类的新方法,因为C++事件处理不了解该Python子类。如果你wx.PyEvent,一个对该 Python实例的引用被保存,并且以后被直接传递给事件处理器,使得该Python代码能被使用。
#2 全局函数wx.NewEventType()的作用类似于wx.NewId();它返回一个唯一的事件类型ID。这个唯一的值标识了一个应用于事件处理系统的事件类型。
#3 这个绑定器对象的创建使用了这个新事件类型作为一个参数。这第二个参数的取值位于[0,2]之间,它代表wxId标识号,该标识号用于wx.EvtHandler.Bind()方法去确定哪个对象是事件的源。
#4 为了创建这个新的更高级的命令事件,程序必需响应特定的用户事件,例如,在每个按钮对象上的鼠标左键按下。依据哪个按钮被敲击,该事件被绑定到OnLeftClick()和OnRightClick()方法。处理器设置了布尔值,以表明按键是否被敲击。
#5 #6 Skip ()的调用允许在该事件处理完成后的进一步处理。在这里,这个新的事件不需要skip调用;它在事件处理器完成之前被分派了(self.OnClick ())。但是所有的鼠标左键按下事件需要调用Skip(),以便处理器不把最后的按钮敲击挂起。这个程序没有处理按钮敲击事件,但是由于使用了Skip (),wxPython在敲击期间使用按钮敲击事件来正确地绘制按钮。如果被挂起了,用户将不会得到来自按钮按下的反馈。
#7 如果两个按钮都被敲击了,该代码创建这个新事件的一个实例。事件类型和两个按钮的ID作为构造器的参数。通常,一个事件类可以有多个事件类型,尽管本例中不是这样。
#8 ProcessEvent ()的调用将这个新事件引入到事件处理系统中,ProcessEvent()的说明见3.4.1节。GetEventHandler()调用返回 wx.EvtHandler的一个实例。大多数情况下,返回的实例是窗口部件对象本身,但是如果其它的wx.EvtHandler()方法已经被压入了事件处理器堆栈,那么返回的将是堆栈项的项目。
#9 该自定义的事件的绑定如同其它事件一样,在这里使用#3所创建的绑定器。
#10 这个例子的事件处理器函数改变窗口的标题以显示敲击数。
至此,你的自定义的事件可以做任何预先存在的wxPython事件所能做的事,比如创建不同的窗口部件,它们响应同样的事件。创建事件是wxPython的定制的一个重要部分。
3.7 总结
1、wxPython应用程序使用基于事件的控制流。应用程序的大部分时间花费在一个主循环中,等待事件并分派它们到适当的处理器函数。
2、所有的wxPython事件是wx.Event类的子类。低级的事件,如鼠标敲击,被用来建立高级的事件,如按钮敲击或菜单项选择。这些由 wxPython窗口部件引起的高级事件是类wx.CommandEvent的子类。大多的事件类通过一个事件类型字段被进一步分类,事件类型字段区分事件。
3、为了捕获事件和函数之间的关联,wxPython使用类wx.PyEventBinder的实例。类 wx.PyEventBinder有许多预定义的实例,每个都对应于一个特定的事件类型。每个wxPython窗口部件都是类wx.EvtHandler 的子类。类wx.EvtHandler有一个方法Bind(),它通常在初始化时被调用,所带参数是一个事件绑定器实例和一个处理器函数。根据事件的类型,别的wxPython对象的ID可能也需要被传递给Bind()调用。
4、事件通常被发送给产生它们的对象,以搜索一个绑定对象,这个绑定对象绑定事件到一个处理器函数。如果事件是命令事件,这个事件沿容器级向上传递直到一个窗口部件被发现有一个针对该事件类型的处理器。一旦一个事件处理器被发现,对于该事件的处理就停止,除非这个处理器调用了该事件的Skip()方法。你可以允许多个处理器去响应一个事件,或去核查该事件的所有默认行为。主循环的某些方面可以使用wx.App的方法来控制。
5、在wxPython中可以创建自定义事件,并作为定制(自定义)的窗口部件的行为的一部分。自定义的事件是类wx.PyEvent的子类,自定义的命令事件是类wx.PyCommandEvent的子类。为了创建一个自定义事件,新的类必须被定义,并且关于每个事件类型(这些事件类型被这个新类所管理)的绑定器必须被创建。最后,这个事件必须在系统的某处被生成,这通过经由 ProcessEvent()方法传递一个新的实例给事件处理器系统来实现。
在本章中,我们已经讨论了应用程序对象,它们对于你的wxPython应用程序是最重要的。在下一章,我们将给你看一个有用的工具,它是用wxPython写成的,它将帮助你使用wxPython进行开发工作。
事件处理是wxPython程序工作的基本机制。主要执行事件处理的工作称为事件驱动。在这章中我们将讨论什么是事件驱动应用程序,它与传统的应用程序有什么不同。我们将对在GUI编程中所使用的概念和术语提供一些介绍,包括与用户交互,工具包和编程逻辑。也将包括典型事件驱动程序的生命周期。
事件就是发生在你的系统中的事,你的应用程序通过触发相应的功能以响应它。事件可以是低级的用户动作,如鼠标移动或按键按下,也可以是高级的用户动作(定义在wxPython的窗口部件中的),如单击按钮或菜单选择。事件可以产生自系统,如关机。你甚至可以创建你自己的对象去产生你自己的事件。wxPython应用程序通过将特定类型的事件和特定的一块代码相关联来工作,该代码在响应事件时执行。事件被映射到代码的过程称为事件处理。
本章将说明事件是什么,你如何写响应一个事件的代码,以及wxPython在事件发生的时候是如何知道去调用你的代码的。我们也将说明如何将定制的事件增加到wxPython库中,该库包含了关于用户和系统行为的标准事件的一个列表。
的但未处理的事件的一个列表。
事件处理器(event handler):响应事件时所调用的函数或方法。也称作处理器函数或处理器方法。
事件绑定器(event binder):一个封装了特定窗口部件,特定事件类型和一个事件处理器的wxPython对象。为了被调用,所有事件处理器必须用一个事件绑定器注册。
wx.EvtHandler:一个wxPython类,它允许它的实例在一个特定类型,一个事件源,和一个事件处理器之间创建绑定。注意,这个类与先前定义的事件处理函数或方法不是同一个东西。
3.2 什么是事件驱动编程?
事件驱动程序主要是一个控制结构,它接受事件并响应它们。wxPython程序(或任何事件驱动程序)的结构与平常的Python脚本不同。标准的Python脚本有一个特定的开始点和结束点,程序员使用条件、循环、和函数来控制执行顺序。
从用户的角度上来看,wxPython程序大部分时间什么也不做,一直闲着直到用户或系统做了些什么来触发这个wxPython程序动作。wxPython 程序的结构就是一个事件驱动程序体系的例子。图3.1是事件处理循环的示意,它展示了主程序的生命、用户事件、和分派到的处理器函数。
事件驱动系统的主循环类似于客户服务呼叫中心的操作者。当没有呼叫的进入的时候,这个操作者处于等待状态。当一个事件发生的时候,如电话铃响了,这个操作者开始一个响应过程,他与客户交谈直到他获得足够的信息以分派该客户给一个合适的回答者。然后操作者等待下一个事件。
尽管每个事件驱动系统之间有一些不同,但它们有很多相似的地方。下面列出了事件驱动程序结构的主要特点:
1、在初始化设置之后,程序的大部分时间花在了一个空闭的循环之中。进入这个循环就标志着程序与用户交互的部分的开始,退出这个循环就标志结束。在 wxPython中,这个循环的方法是:wx.App.MainLoop(),并且在你的脚本中显式地被调用。当所有的顶级窗口关闭时,主循环退出。
2、程序包含了对应于发生在程序环境中的事情的事件。事件通常由用户的行为触发,但是也可以由系统的行为或程序中其他任意的代码。在wxPython中,所有的事件都是类wx.Event或其子类的一个实例。每个事件都有一个事件类型属性,它使得不同的事件能够被辨别。例如,鼠标释放和鼠示按下事件都被认为是同一个类的实例,但有不同的事件类型。
3、作为这个空闭的循环部分,程序定期检查是否有任何请求响应事情发生。有两种机制使得事件驱动系统可以得到有关事件的通知。最常被wxPython使用的方法是,把事件传送到一个中心队列,由该队列触发相应事件的处理。另一种方法是使用轮询的方法,所有可能引发事件的事件主被主过程定期查询并询问是否有没有处理的事件。
4、当事件发生时,基于事件的系统试着确定相关代码来处理该事件,如果有,相关代码被执行。在wxPython中,原系统事件被转换为wx.Event实例,然后使用 wx.EvtHandler.ProcessEvent()方法将事件分派给适当的处理器代码。图3.3呈现了这个过程:
事件机制的组成部分是事件绑定器对象和事件处理器。事件绑定器是一个预定义的wxPython对象。每个事件都有各自的事件绑定器。事件处理器是一个函数或方法,它要求一个wxPython事件实例作为参数。当用户触发了适当的事件时,一个事件处理器被调用。
下面我们将讨论有关wxPython更多的细节,我们把事件响应的基本单元“事件处理器”作为开始。
3.2.1 编写事件处理器
在你的wxPython代码中,事件和事件处理器是基于相关的窗口部件的。例如,一个按钮被单击被分派给一个基于该按钮的专用的事件处理器。为了要把一个来自特定窗口部件的事件绑定到一个特定的处理器方法,你要使用一个绑定器对象来管理这个连接。例如:
self.Bind(wx.EVT_BUTTON, self.OnClick, aButton)
上例使用了预定义的事件绑定器对象wx.EVT_BUTTON来将aButton对象上的按钮单击事件与方法self.OnClick相关联起来。这个 Bind()方法是wx.EvtHandler的一个方法,wx.EvtHandler是所有可显示对象的父类。因此上例代码行可以被放置在任何显示类。
即使你的wxPython程序表面上看起来在被动地等待事件,但它仍在做事。它在运行方法wx.App.MainLoop(),该方法是一个无限的循环。MainLoop()方法可以使用Python伪代码表示如下:
while True:
while not self.Pending():
self.ProcessIdle()
self.DoMessage()
上面的伪代码意思是如果没有未处理的消息,则做一些空闲时做的事;如果有消息进入,那么将这个消息分派给适当的事件处理方法。
3.2.2 设计事件驱动程序
对于事件驱动程序的设计,由于没有假设事件何时发生,所以程序员将大量的控制交给了用户。你的wxPython程序中的大多数代码通过用户或系统的行为被直接或间接地执行。例如在用户选择了一个菜单项、或按下一个工具栏按钮、或按下了特定的按键组合后,你的程序中有关保存工作的代码被执行了。
另一方面,事件驱动体系通常是分散性的。响应一个窗口部件事件的代码通常不是定义在该部件的定义中的。例如,响应一个按钮单击事件的代码不必是该按钮定义的一部分,而可以存在在该按钮所附的框架中或其它地方。当与面向对象设计结合时,这个体系导致了松散和高度可重用的代码。你将会发现Python的灵活使得重用不同的wxPython应用程序的通常的事件处理器和结构变得非常容易。
3.2.3 事件触发
在wxPython 中,大部分窗口部件在响应低级事件时都导致高级事件发生。例如,在一个wx.Button上的鼠标单击导致一个EVT_BUTTON事件的生成,该事件是 wx.CommandEvent的特定类型。类似的,在一个窗口的角中拖动鼠标将导致wxPython为你自动创建一个wx.SizeEvent事件。高级事件的用处是让你的系统的其它部分更容易聚焦于最有关联的事件上,而不是陷于追踪每个鼠标单击。高级事件能够封装更多关于事件的有用的信息。当你创建你自已的定制的窗口部件时,你能定义你自己的定制事件以便管理事件的处理。
在wxPython中,代表事件的是事件对象。事件对象是类wx.Event或其子类的一个实例。父类wx.Event相对小且抽象,它只是包含了对所有事件的一些通常的信息。wx.Event的各个子类都添加了更多的信息。
在wxPython中,有一些wx.Event的子类。表3.2包含了你将最常遇到的一些事件类。记住,一个事件类可以有多个事件类型,每个都对应于一个不同的用户行为。下表3.2是wx.Event的重要的子类。
wx.CloseEvent:当一个框架关闭时触发。这个事件的类型分为一个通常的框架关闭和一个系统关闭事件。
wx.CommandEvent:与窗口部件的简单的各种交互都将触发这个事件,如按钮单击、菜单项选择、单选按钮选择。这些交互有它各自的事件类型。许多更复杂的窗口部件,如列表等则定义wx.CommandEvent的子类。事件处理系统对待命令事件与其它事件不同。
wx.KeyEvent:按按键事件。这个事件的类型分按下按键、释放按键、整个按键动作。
wx.MouseEvent:鼠标事件。这个事件的类型分鼠标移动和鼠标敲击。对于哪个鼠标按钮被敲击和是单击还是双击都有各自的事件类型。
wx.PaintEvent:当窗口的内容需要被重画时触发。
wx.SizeEvent:当窗口的大小或其布局改变时触发。
wx.TimerEvent:可以由类wx.Timer类创建,它是定期的事件。
通常,事件对象需要使用事件绑定器和事件处理系统将它们传递给相关的事件处理器。
3.3 如何将事件绑定到处理器?
事件绑定器由类wx.PyEventBinder的实例组成。一个预定义的wx.PyEventBinder的实例被提供给所有支持的事件类型,并且在你需要的时候你可以为你定制的事件创建你自己的事件绑定器。每个事件类型都有一个事件绑定器,这意味着一个wx.Event的子类对应多个绑定器。
在wxPython中,事件绑定器实例的名字是全局性的。为了清楚地将事件类型与处理器联系起来,它们的名字都是以wx.EVT_开头并且对应于使用在C++ wxWidgets代码中宏的名字。值得强调的是,wx.EVT绑定器名字的值不是你通过调用一个wx.Event实例的GetEventType()方法得到的事件类型的实际的整数码。事件类型整数码有一套完全不同的全局名,并且在实际中不常被使用。
作为wx.EVT名字的例子,让我们看看wx.MouseEvent的事件类型。正如我们所提到的,它们有十四个,其中的九个涉及到了基于在按钮上的敲击,如鼠标按下、鼠标释放、或双击事件。这九个事件类型使用了下面的名字:
wx.EVT_LEFT_DOWN
wx.EVT_LEFT_UP
wx.EVT_LEFT_DCLICK
wx.EVT_MIDDLE_DOWN
wx.EVT_MIDDLE_UP
wx.EVT_MIDDLE_DCLICK
wx.EVT_RIGHT_DOWN
wx.EVT_RIGHT_UP
wx.EVT_RIGHT_DCLICK
另外,类型wx.EVT_MOTION产生于用户移动鼠标。类型wx.ENTER_WINDOW和wx.LEAVE_WINDOW产生于当鼠标进入或离开一个窗口部件时。类型wx.EVT_MOUSEWHEEL被绑定到鼠标滚轮的活动。最后,你可以使用类型wx.EVT_MOUSE_EVENTS一次绑定所有的鼠标事件到一个函数。
同样,类wx.CommandEvent有28个不同的事件类型与之关联;尽管有几个仅针对老的Windows 操作系统。它们中的大多数是专门针对单一窗口部件的,如wx.EVT_BUTTON用于按钮敲击,wx.EVT_MENU用于菜单项选择。用于专门窗口部件的命令事件在part2中讨论。
绑定机制的好处是它使得wxPython可以很细化地分派事件,而仍然允许同类的类似事件发生并且共享数据和功能。这使得在wxPython中写事件处理比在其它界面工具包中清细得多。
事件绑定器被用于将一个wxPython窗口部件与一个事件对象和一个处理器函数连接起来。这个连接使得wxPython系统能够通过执行处理器函数中的代码来响应相应窗口部件上的事件。在wxPython中,任何能够响应事件的对象都是wx.EvtHandler的子类。所有窗口对象都是 wx.EvtHandler的子类,因些在wxPython应用程序中的每个窗口部件都能够响应事件。类wx.EvtHandler也能够被非窗口部件对象所使用,如wx.App,因此事件处理功能不是限于可显示的窗口部件。我们所说的窗口部件能响应事件的意思是:该窗口部件能够创建事件绑定,在分派期间 wxPython能够识别该事件绑定。由绑定器调用的在事件处理器函数中的实际代码不是必须位于一个wx.EvtHandler类中。
3.3.1 使用wx.EvtHandler的方法工作
wx.EvtHandler类定义的一些方法在一般情况下用不到。你会经常使用的wx.EvtHandler的方法是Bind(),它创建事件绑定。该方法的用法如下:
Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)
Bind ()函数将一个事件和一个对象与一个事件处理器函数关联起来。参数event是必选的,它是我们在3.3节中所说的wx.PyEventBinder的一个实例。参数handler也是必选的,它是一个可调用的Python对象,通常是一个被绑定的方法或函数。处理器必须是可使用一个参数(事件对象本身)来调用的。参数handler可以是None,这种情况下,事件没有关联的处理器。参数source是产生该事件的源窗口部件,这个参数在触发事件的窗口部件与用作事件处理器的窗口部件不相同时使用。通常情况下这个参数使用默认值None,这是因为你一般使用一个定制的wx.Frame类作为处理器,并且绑定来自于包含在该框架内的窗口部件的事件。父窗口的__init__是一个用于声明事件绑定的方便的位置。但是如果父窗口包含了多个按钮敲击事件源(比如OK按钮和Cancel按钮),那么就要指定source参数以便wxPython区分它们。下面是该方法的一个例子:
self.Bind(wx.EVT_BUTTON, self.OnClick, button)
下例3.1演示了使用参数source和不使用参数source的方法,它改编自第二章中的代码:
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
panel = wx.Panel(self, -1)
button = wx.Button(panel, -1, "Close", pos=(130, 15),
size=(40, 40))
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) #1 绑定框架关闭事件
self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) #2 绑定按钮事件
def OnCloseMe(self, event):
self.Close(True)
def OnCloseWindow(self, event):
self.Destroy()
说明:
#1 这行绑定框架关闭事件到self.OnCloseWindow方法。由于这个事件通过该框架触发且用于帧,所以不需要传递一个source参数。
#2 这行将来自按钮对象的按钮敲击事件绑定到self.OnCloseMe方法。这样做是为了让wxPython能够区分在这个框架中该按钮和其它按钮所产生的事件。
你也可以使用source参数来标识项目,即使该项目不是事件的源。例如,你可以绑定一个菜单事件到事件处理器,即使这个菜单事件严格地说是由框架所触发的。下例3.2演示了绑定一个菜单事件的例子:
#!/usr/bin/env python
import wx
class MenuEventFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Menus',
size=(300, 200))
menuBar = wx.MenuBar()
menu1 = wx.Menu()
menuItem = menu1.Append(-1, "&Exit...")
menuBar.Append(menu1, "&File")
self.SetMenuBar(menuBar)
self.Bind(wx.EVT_MENU, self.OnCloseMe, menuItem)
def OnCloseMe(self, event):
self.Close(True)
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MenuEventFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
Bind ()方法中的参数id和id2使用ID号指定了事件的源。一般情况下这没必要,因为事件源的ID号可以从参数source中提取。但是某些时候直接使用 ID是合理的。例如,如果你在使用一个对话框的ID号,这比使用窗口部件更容易。如果你同时使用了参数id和id2,你就能够以窗口部件的ID号形式将这两个ID号之间范围的窗口部件绑定到事件。这仅适用于窗口部件的ID号是连续的。
注意:Bind()方法出现在wx.Python2.5中,以前版本的事件绑定中,EVT_*的用法如同函数对象,因此你会看到如下的绑定调用:
wx.EVT_BUTTON(self, self.button.GetId(), self.OnClick)
这个方式的缺点是它不像是面向对象的方法调用。然而,这个老的样式仍可工作在2.5的版本中(因为wx.EVT*对象仍是可调用的)。
下表3.3列出了最常使用的wx.EvtHandler的方法:
AddPendingEvent(event):将这个event参数放入事件处理系统中。类似于ProcessEvent(),但它实际上不会立即触发事件的处理。相反,该事件被增加到事件队列中。适用于线程间的基于事件的通信。
Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY):完整的说明见3.3.1节。
GetEvtHandlerEnabled()
SetEvtHandlerEnabled( boolean):如果处理器当前正在处理事件,则属性为True,否则为False。
ProcessEvent(event):把event对象放入事件处理系统中以便立即处理。
3.4 wxPython是如何处理事件的?
基于事件系统的关键组成部分是事件处理。通过它,一个事件被分派到了相应的用于相应该事件的一块代码。在这一节,我们将讨论wxPython处理事件的过程。我们将使用小段的代码来跟踪这个处理的步骤。图 3.2显示了一个带有一个按钮的简单窗口,这个按钮将被用来产生一个简单的事件。
下例3.3包含了生成这个窗口的代码。在这个代码中,通过敲击按钮和将鼠标移动到按钮上都可产生wxPython事件。
例3.3绑定多个鼠标事件
#!/usr/bin/env python
import wx
class MouseEventFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
self.panel = wx.Panel(self)
self.button = wx.Button(self.panel,
label="Not Over", pos=(100, 15))
self.Bind(wx.EVT_BUTTON, self.OnButtonClick,
self.button) #1 绑定按钮事件
self.button.Bind(wx.EVT_ENTER_WINDOW,
self.OnEnterWindow) #2 绑定鼠标位于其上事件
self.button.Bind(wx.EVT_LEAVE_WINDOW,
self.OnLeaveWindow) #3 绑定鼠标离开事件
def OnButtonClick(self, event):
self.panel.SetBackgroundColour('Green')
self.panel.Refresh()
def OnEnterWindow(self, event):
self.button.SetLabel("Over Me!")
event.Skip()
def OnLeaveWindow(self, event):
self.button.SetLabel("Not Over")
event.Skip()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MouseEventFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
说明:
MouseEventFrame包含了一个位于中间的按钮。在其上敲击鼠标将导致框架的背景色改变为绿色。#1绑定了鼠标敲击事件。当鼠标指针位于这个按钮上时,按钮上的标签将改变,这用#2绑定。当鼠标离开这个按钮时,标签变回原样,这用#3绑定。
通过观察上面的鼠标事件例子,我们引出了在wxPython中的事件处理的一些问题。#1中,按钮事件由附着在框架上的按钮触发,那么wxPython怎么知道在框架对象中查找绑定而不是在按钮对象上呢?在#2和#3中,鼠标的进入和离开事件被绑定到了按钮,为什么这两个事件不能被绑到框架上呢。这些问题将通过检查wxPython用来决定如何响应事件的过程来得到回答。
3.4.1 理解事件处理过程
wxPython的事件处理过程被设计来简化程序员关于事件绑定的创建,使他们不必考虑哪些不重要的事件。
隐藏在简化设计之下的底层机制是有些复杂的。接下来,我们将跟踪关于按钮敲击和鼠标进入事件的过程。
图3.3显示了事件处理过程的一个基本的流程。矩形代表过程的开始和结束,环形代表各种wxPython对象(它们是这个过程的一部分),棱形代表判断点,带条的矩形代表实际的事件处理方法。
事件处理过程开始于触发事件的对象。通常,wxPython首先在触发对象中查找匹配事件类型的被绑定的处理器函数。如果找到,则相应的方法被执行。否则,wxPython将检查该事件是否传送到了上一级的容器。如果是的话,父窗口部件将被检查,这样一级一级向上寻找,直到wxPython找到了一个处理器函数或到达了顶级窗口。如果事件没有传播,在处理过程完成之前,wxPython仍将为了处理器函数而检查应用程序对象。
当事件处理器运行时,过程通常就结束了。然而,函数可以告诉wxPython去继续查找处理器。
下面让我们仔细观察一下这个过程的每一个步骤。我们的每步分析都有图3.3的一个相关略图。
第一步,创建事件
这个过程开始于事件被创建时。
在wxPython架构中已经创建了大多数的事件类型,它们用于响应特定的用户动作或系统通知。例如,当wxPython通知“鼠标移进了一个新窗口部件对象时”,鼠标进入事件被触发,鼠标敲击事件在鼠标按下或释放后被创建。
事件首先被交给创建事件的对象。对于按钮敲击,这个对象是按钮;对于鼠标进入事件,这个对象是所进入的窗口部件。
第二步,确定事件对象是否被允许处理事件。
事件处理过程检查的下一步是看相关窗口部件当前是否被允许去处理事件。
通过调用wx.EvtHandler的SetEvtHandlerEnabled(boolean)方法,一个窗口可以被设置为允许或不允许事件处理。不允许事件处理的结果是该窗口部件在事件处理中被完全绕过,与该对象关联的绑定对象也不会被搜索,并且在这步中的处理没有向下的分支。
在事件处理器级使一个窗口部件有效或无效与在用户界面级(UI)不一样。在UI级使一个窗口部件无效或有效,使用wx.Window的方法Disable()和 Enable()。在UI级使一个窗口部件无效意味用户不能与这个无效的窗口部件交互。通常无效的窗口部件在屏幕上以灰化的状态表示。一个在UI级无效的窗口不能产生任何事件;但是,如果它对于别的事件是容器的级别,它仍然能够处理它接受到的事件。本节的剩余内容,我们将在wx.EvtHandler层面上使用有效和无效,这涉及到窗口部件是否被允许处理事件。
第三步 定位绑定器对象
如图3.6所示
然后ProcessEvent()方法寻找一个绑定器对象,该绑定器对象确定当前对象和事件类型之间的绑定。
如果对象自身的绑定器没有被找到,那么向上到该对象的超类中去寻找。如果一个绑定器对象被发现,wxPython调用相关的处理器函数。在处理器被调用后,该事件的事件处理停止,除非处理器函数显式地要求作更多的处理。
在例子3.3中,因为在按钮对象,绑定器对象wx.EVT_ENTER_WINDOW,和相关的方法OnEnterWindow()之间定义了绑定,所以鼠标进入事件被捕获,OnEnterWindow()方法被调用。由于我们没有绑定鼠标敲击事件
wx.EVT_LEFT_DOWN,在这种情况下,wxPython将继续搜索。
第四步 决定是否继续处理
如图3.7所示
在调用了第一个事件处理器之后,wxPython查看是否有进一步的处理要求。事件处理器通过调用wx.Event的方法Skip()要求更多的处理。如果Skip()方法被调用,那么处理将继续,并且任何定义在超类中的处理器在这一步中被发现并执行。Skip()方法在处理中的任一点或处理器所调用的任何代码中都可以被调用。Skip()方法在事件实例中设置一个标记,在事件处理器方法完成后,wxPython检查这个标记。在例3.3中, OnButtonClick()不调用Skip(),因此在那种情况下,处理器方法结束后,事件处理完成。在另两个事件处理器中调用了Skip(),所以系统将保持搜索“匹配事件绑定”,最后对于原窗口部件的鼠标进入和离开事件调用默认的功能,如鼠标位于其上的事件。
第五步 决定是否展开
如图3.8所示
最后,wxPython决定是否将事件处理向上展开到容器级以发现一个事件处理器。所谓的容器级是从一个特定的窗口部件到顶层框架的路径,这个路径是从窗口部件到它的父容器,一直向上沿升。
如果当前对象没有关于该事件的一个处理器,或如果处理器调用了Skip(),wxPython将决定是否这个事件将沿容器级向上展开。如果决定不,那么在 wx.App实例中再找寻一次处理器,然后停止。如果决定是,则事件处沿该窗口的容器级向上搜索,直到发现适当的绑定,或到达顶层框架对象,或到达一个 wx.Dialog对象(即使这个对话框不是顶级的)。如果ProcessEvent()返回True,事件则被认为发现了一个适当的绑定,这表示处理完成。到达一个wx.Dialog停止的目的是防止父框架被来自对话框的无关的或未预期的假事件干扰。
一个事件是否向上展开至容器级,这是每个事件实例的一个动态属性,尽管实际上默认值几乎总是使用那几个。默认情况,只有wx.CommandEvent及其子类的实例向上展开至容器级。其它的所有事件不这样做。
在例3.3中,按钮敲击事件得到处理。在wx.Button上敲击鼠标产生一个命令类型的事件wx.EVT_BUTTON。由于wx.EVT_BUTTON 属于一个wx.CommandEvent,所以wxPython在这个按钮对象中找寻绑定失败后,它将向上展开至容器级,先是按钮的父窗口panel。由于panel中没有相匹配的绑定,所以又向上至panel的父窗口frame。由于frame中有匹配的绑定,所以ProcessEvent()调用相关函数 OnButtonClick()。
第五步同时也说明了为什么鼠标进入和离开事件必须被绑定到按钮而不是框架。由于鼠标事件不是 wx.CommandEvent的子类,所以鼠标进入和离开事件不向上展开至容器级。如果鼠标进入和离开事件被绑定到了框架,那么当鼠标进入或离开框架时,wxPython触发鼠标进入或离开事件。
在这种方式中,命令事件是被优先对待的。因为它们被认为是高级事件,表示用户正在应用程序空间中做一些事,而非窗口系统。窗口系统类型事件只对窗口部件感兴趣,而应用级事件对容器级。这个规则不防碍我们在任何地方声明绑定,不管被绑定的是什么对象或什么对象定义事件处理器。例如,即使这个绑定的鼠标敲击事件针对于按钮对象,而绑定则被定义在这个框架类中,且调用这个框架内的方法。换句话说,低级的非命令事件通常用于窗口部件或一些系统级的通知,如鼠标敲击、按键按下、绘画请求、调整大小或移动。另一方面,命令事件,如在按钮上敲击鼠标、或列表框上的选择,通常由窗口部件自己生成。例如,在适当的窗口部件上按下和释放鼠标后,按钮命令事件产生。
最后,如果遍历了容器级后,事件没有被处理,那么应用程序的wx.App对象调用ProcessEvent()。默认情况下,这什么也不做,但是你可以给你的wx.App增加事件绑定,以便以非标准的方式来传递事件。例如,假如你在写一个GUI构建器,你可能想把你构建器窗口中的事件传到你的代码窗口中,即使它们都是顶级窗口。方法之一是捕获应用程序对象中的事件,并把它们传递到代码窗口上。
3.4.2 使用Skip()方法
事件的第一个处理器函数被发现并执行完后,该事件处理将终止,除非在处理器返回之前调用了该事件的Skip()方法。调用Skip()方法允许另外被绑定的处理器被搜索,搜索依据3.4.1节中的第四步中声明的规则,因此父类和父窗口被搜索,就如同这第一个处理器不存在一样。在某些情况下,你想继续处理事件,以便原窗口部件的默认行为和你定制的处理能被执行。例3.4显示了一个使用Skip()的例子,它使得程序能够同时响应同一按钮上的鼠标左按键按下和按钮敲击。
例3.4 同时响应鼠标按下和按钮敲击
#!/usr/bin/env python
import wx
class DoubleEventFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
self.panel = wx.Panel(self, -1)
self.button = wx.Button(self.panel, -1, "Click Me", pos=(100, 15))
self.Bind(wx.EVT_BUTTON, self.OnButtonClick,
self.button) #1 绑定按钮敲击事件
self.button.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) #2 绑定鼠标左键按下事件
def OnButtonClick(self, event):
self.panel.SetBackgroundColour('Green')
self.panel.Refresh()
def OnMouseDown(self, event):
self.button.SetLabel("Again!")
event.Skip() #3 确保继续处理
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = DoubleEventFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
#1 这行绑定按钮敲击事件到OnButtonClick()处理器,这个处理器改变框架的背景色。
#2 这行绑定鼠标左键按下事件到OnMouseDown()处理器,这个处理器改变按钮的标签文本。由于鼠标左键按下事件不是命令事件,所以它必须被绑定到按钮(self.button.Bind)而非框架(self.Bind)。
当用户在按钮上敲击鼠标时,通过直接与底层操作系统交互,鼠标左键按下事件首先被产生。通常情况下,鼠标左键按下事件改变按钮的状态,随着鼠标左键的释放,产生了wx.EVT_BUTTON敲击事件。由于行#3的Skip()语句,DoubleEventFrame维持处理。没有Skip()语句,事件处理规则发现在#2创建的绑定,而在按钮能产生wx.EVT_BUTTON事件之前停止。由于Skip()的调用,事件处理照常继续,并且按钮敲击被创建。
记住,当绑定低级事件时如鼠标按下或释放,wxPython期望捕获这些低级事件以便生成进一步的事件,为了进一步的事件处理,你必须调用Skip()方法,否则进一步的事件处理将被阻止。
对于初始对象有效或无效状态的检查,这发生在ProcessEvent()方法中,该方法由wxPython系统调用以开始和处理事件分配机制。我们将在事件处理过程中一再看到ProcessEvent()方法,它是类 wx.EvtHandler中的方法,它实际上执行图3.3所描绘的大量事件处理。如果ProcessEvent()方法最后完成了事件处理,则 ProcessEvent()返回True。如果一个处理器被发现和组合事件被处理,则认为处理完成。处理器函数可以通过调用wx.Event的Skip ()方法来显式地请求进一步的处理。另处,如果初始对象是wx.Window的一个子类,那么它能够使用一个称为validator的对象来过滤事件。 Validator将在第九章中详细讨论。
3.5 在应用程序对象中还包含哪些其它的属性?
要更直接地管理主事件循环,你可以使用一些 wx.App方法来修改它。例如,按你的计划,你可能想开始处理下一个有效的事件,而非等待wxPython去开始处理。如果你正在执行一个长时间的过程,并且不想图形界面被冻结,那么这个特性是必要的,通常你不需要使用这节中的这些方法,但是,这些性能有时是很重要的。
下表3.4列出了你可以用来修改主循环的wx.App方法:
Dispatch():迫使事件队列中的下一个事件被发送。通过MainLoop()使用或使用在定制的事件循环中。
Pending():如果在wxPython应用程序事件队列中有等待被处理的事件,则返回True。
Yield(onlyIfNeeded=False):允许等候处理的wxWidgets事件在一个长时间的处理期间被分派,否则窗口系统将被锁定而不能显示或更新。如果等候处理的事件被处理了,则返回True,否则返回False。
onlyIfNeeded参数如果为True,那么当前的处理将让位于等候处理的事件。如果该参数为False,那么递归调用Yield是错误的。
这里也有一个全局函数wx.SafeYield(),它阻止用户在Yield期间输入数据(这通过临时使用来输入的窗口部件无效来达到目的),以免干扰Yield任务。
另一管理事件的方法是通过定制的方式,它创建你自己的事件类型,以匹配你的应用程序中特定的数据和窗口部件。下一节我们将讨论如何创建你自己的定制事件。
3.6 如何创建自己的事件?
尽管这是一个更高级的主题,但是我们将在这里讨论定制事件。当你第一次阅读的时候,你可以跳过并且以后再回过头来读。
为了要与wxPython提供的事件类相区别,你可以创建你自己定制的事件。你可以定制事件以响应哪些针对你的应用程序的数据更新或其它改变,此处定制的事件必须负责你的自定义数据。创建定制的事件类的另一个原因是:你可以针对所定制的窗口部件,使用它自己独特的命令事件类型。下一节中,我们将看一个定制窗口部件的例子。
3.6.1 为一个定制的窗口部件定义一个定制的事件。
图3.9显示了这个窗口部件,一个画板(panel)包含了两个按钮。自定义的事件TwoButtonEvent仅当用户敲击了这两个按钮之后被触发。这个事件包含了一个关于用户在该部件上敲击次数的计数。
创建自定义事件的步骤:
1、定义一个新的事件类,它是wxPython的wx.PyEvent类的子类。如果你想这个事件被作为命令事件,你可以创建 wx.PyCommandEvent的子类。像许多wxPython中的覆盖一样,一个类的py版本使得wxWidget系统明白用Python写的覆盖 C++方法的方法。
2、创建一个事件类型和一个绑定器对象去绑定该事件到特定的对象。
3、添加能够建造这个新事件实例的代码,并且使用ProcessEvent()方法将这个实例引入事件处理系统。一旦该事件被创建,你就可以像使用其它的wxPython事件一样创建绑定和处理器方法。
下例3.5显示了管理窗口部件的代码:
import wx
class TwoButtonEvent(wx.PyCommandEvent): #1 定义事件
def __init__(self, evtType, id):
wx.PyCommandEvent.__init__(self, evtType, id)
self.clickCount = 0
def GetClickCount(self):
return self.clickCount
def SetClickCount(self, count):
self.clickCount = count
myEVT_TWO_BUTTON = wx.NewEventType() #2 创建一个事件类型
EVT_TWO_BUTTON = wx.PyEventBinder(myEVT_TWO_BUTTON, 1) #3 创建一个绑定器对象
class TwoButtonPanel(wx.Panel):
def __init__(self, parent, id=-1, leftText="Left",
rightText="Right"):
wx.Panel.__init__(self, parent, id)
self.leftButton = wx.Button(self, label=leftText)
self.rightButton = wx.Button(self, label=rightText,
pos=(100,0))
self.leftClick = False
self.rightClick = False
self.clickCount = 0
#4 下面两行绑定更低级的事件
self.leftButton.Bind(wx.EVT_LEFT_DOWN, self.OnLeftClick)
self.rightButton.Bind(wx.EVT_LEFT_DOWN, self.OnRightClick)
def OnLeftClick(self, event):
self.leftClick = True
self.OnClick()
event.Skip() #5 继续处理
def OnRightClick(self, event):
self.rightClick = True
self.OnClick()
event.Skip() #6 继续处理
def OnClick(self):
self.clickCount += 1
if self.leftClick and self.rightClick:
self.leftClick = False
self.rightClick = False
evt = TwoButtonEvent(myEVT_TWO_BUTTON, self.GetId()) #7 创建自定义事件
evt.SetClickCount(self.clickCount) # 添加数据到事件
self.GetEventHandler().ProcessEvent(evt) #8 处理事件
class CustomEventFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Click Count: 0',
size=(300, 100))
panel = TwoButtonPanel(self)
self.Bind(EVT_TWO_BUTTON, self.OnTwoClick, panel) #9 绑定自定义事件
def OnTwoClick(self, event): #10 定义一个事件处理器函数
self.SetTitle("Click Count: %s" % event.GetClickCount())
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = CustomEventFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
说明:
#1 这个关于事件类的构造器声明为wx.PyCommandEvent的一个子类。 wx.PyEvent和wx.PyCommandEvent是 wxPython特定的结构,你可以用来创建新的事件类并且可以把C++类和你的Python代码连接起来。如果你试图直接使用wx.Event,那么在事件处理期间wxPython不能明白你的子类的新方法,因为C++事件处理不了解该Python子类。如果你wx.PyEvent,一个对该 Python实例的引用被保存,并且以后被直接传递给事件处理器,使得该Python代码能被使用。
#2 全局函数wx.NewEventType()的作用类似于wx.NewId();它返回一个唯一的事件类型ID。这个唯一的值标识了一个应用于事件处理系统的事件类型。
#3 这个绑定器对象的创建使用了这个新事件类型作为一个参数。这第二个参数的取值位于[0,2]之间,它代表wxId标识号,该标识号用于wx.EvtHandler.Bind()方法去确定哪个对象是事件的源。
#4 为了创建这个新的更高级的命令事件,程序必需响应特定的用户事件,例如,在每个按钮对象上的鼠标左键按下。依据哪个按钮被敲击,该事件被绑定到OnLeftClick()和OnRightClick()方法。处理器设置了布尔值,以表明按键是否被敲击。
#5 #6 Skip ()的调用允许在该事件处理完成后的进一步处理。在这里,这个新的事件不需要skip调用;它在事件处理器完成之前被分派了(self.OnClick ())。但是所有的鼠标左键按下事件需要调用Skip(),以便处理器不把最后的按钮敲击挂起。这个程序没有处理按钮敲击事件,但是由于使用了Skip (),wxPython在敲击期间使用按钮敲击事件来正确地绘制按钮。如果被挂起了,用户将不会得到来自按钮按下的反馈。
#7 如果两个按钮都被敲击了,该代码创建这个新事件的一个实例。事件类型和两个按钮的ID作为构造器的参数。通常,一个事件类可以有多个事件类型,尽管本例中不是这样。
#8 ProcessEvent ()的调用将这个新事件引入到事件处理系统中,ProcessEvent()的说明见3.4.1节。GetEventHandler()调用返回 wx.EvtHandler的一个实例。大多数情况下,返回的实例是窗口部件对象本身,但是如果其它的wx.EvtHandler()方法已经被压入了事件处理器堆栈,那么返回的将是堆栈项的项目。
#9 该自定义的事件的绑定如同其它事件一样,在这里使用#3所创建的绑定器。
#10 这个例子的事件处理器函数改变窗口的标题以显示敲击数。
至此,你的自定义的事件可以做任何预先存在的wxPython事件所能做的事,比如创建不同的窗口部件,它们响应同样的事件。创建事件是wxPython的定制的一个重要部分。
3.7 总结
1、wxPython应用程序使用基于事件的控制流。应用程序的大部分时间花费在一个主循环中,等待事件并分派它们到适当的处理器函数。
2、所有的wxPython事件是wx.Event类的子类。低级的事件,如鼠标敲击,被用来建立高级的事件,如按钮敲击或菜单项选择。这些由 wxPython窗口部件引起的高级事件是类wx.CommandEvent的子类。大多的事件类通过一个事件类型字段被进一步分类,事件类型字段区分事件。
3、为了捕获事件和函数之间的关联,wxPython使用类wx.PyEventBinder的实例。类 wx.PyEventBinder有许多预定义的实例,每个都对应于一个特定的事件类型。每个wxPython窗口部件都是类wx.EvtHandler 的子类。类wx.EvtHandler有一个方法Bind(),它通常在初始化时被调用,所带参数是一个事件绑定器实例和一个处理器函数。根据事件的类型,别的wxPython对象的ID可能也需要被传递给Bind()调用。
4、事件通常被发送给产生它们的对象,以搜索一个绑定对象,这个绑定对象绑定事件到一个处理器函数。如果事件是命令事件,这个事件沿容器级向上传递直到一个窗口部件被发现有一个针对该事件类型的处理器。一旦一个事件处理器被发现,对于该事件的处理就停止,除非这个处理器调用了该事件的Skip()方法。你可以允许多个处理器去响应一个事件,或去核查该事件的所有默认行为。主循环的某些方面可以使用wx.App的方法来控制。
5、在wxPython中可以创建自定义事件,并作为定制(自定义)的窗口部件的行为的一部分。自定义的事件是类wx.PyEvent的子类,自定义的命令事件是类wx.PyCommandEvent的子类。为了创建一个自定义事件,新的类必须被定义,并且关于每个事件类型(这些事件类型被这个新类所管理)的绑定器必须被创建。最后,这个事件必须在系统的某处被生成,这通过经由 ProcessEvent()方法传递一个新的实例给事件处理器系统来实现。
在本章中,我们已经讨论了应用程序对象,它们对于你的wxPython应用程序是最重要的。在下一章,我们将给你看一个有用的工具,它是用wxPython写成的,它将帮助你使用wxPython进行开发工作。
欢迎来到wxPython
下面是一个例子,它创建了一个有一个文本框的窗口用来显示鼠标的位置。
#!/bin/env python
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "My Frame", size=(300, 300))
panel = wx.Panel(self, -1)
panel.Bind(wx.EVT_MOTION, self.OnMove)
wx.StaticText(panel, -1, "Pos:", pos=(10, 12))
self.posCtrl = wx.TextCtrl(panel, -1, "", pos=(40, 10))
def OnMove(self, event):
pos = event.GetPosition()
self.posCtrl.SetValue("%s, %s" % (pos.x, pos.y))
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show(True)
app.MainLoop()
图示如下:
漂亮的界面是一个GUI程序必不可少的一部分,wxPython可以做到这一点,加之Python强大的功能和简洁的语法,使用得它在Python的gui中成为一种主流。
1.1 开始wxPython
首先我们创建一个显示一个图像的文件。这将分三步:
1、首先创建一个空的最小的可以工作的wxPthon程序
2、组织和细化
3、显示wxPython的logo
图示如下:
1.2 创建最小的空的wxPython程序
我们创建一个名为bare.py的程序并键入以下代码:
import wx #1
class App(wx.App): #2
def OnInit(self): #3
frame = wx.Frame(parent=None, title='Bare')
frame.Show()
return True
app = App() #4
app.MainLoop() #5
上面的代码运行的结果如下:
上面的代码的任何一行都不能少,否则将不能工作。这个基本的wxPython程序说明了开发任一wxPython程序所必须的五个基本步骤:
1、导入必须的wxPython包
2、子类化wxPython应用程序类
3、定义一个应用程序的初始化方法
4、创建一个应用程序类的实例
5、进入这个应用程序的主事件循环
下面让我们看看这个最小的空的程序是如何一步一步实现的。
1.2.1 导入wxPython
你需要做的第一件事就是导入这个主要的wxPython包,这个包名为wx:
import wx
一旦这个包被导入,你就可以引用wxPython的类、函数和常量(它们以wx为前缀),如下所示:
class App(wx.App):
注 意:老的引入方式仍然被支持,你可能会遇到用这种老的引入方式的代码。因此我们将会简短地说明这种老的方式及为什么要改变它。老的包的名字是 wxPython,它包含了一个内在的名为wx模块。那时,通常有两种导入必要的代码的方法,一种就是从wxPython包中导入wx模块: from wxPython import wx;另一种就是直接从wx模块中导入所有的东西:from wxPython.wx import *。这 两种方法都有严重的缺点。这第二种方法Python中是不建议使用的,这因为可能导致名字空间冲突,而老的wx模块通过在其属性前加一个wx前缀避免了这 个问题。尽管使用这个安全防范,但是import*仍然有可能导致问题,但是许多wxPython程序员喜欢这种类型,并且你将在老的代码中经常看到这种 用法。这种风格的坏处是类名以小写字母开头,而大多数wxPython方法以大写字母开头,这和通常的Python编写程序的习惯相反。
然而如果你试图避免由于使用import*导致的名字空间膨胀,而使用from wxPython import wx。那么你就不得不为每个类、函数、常数名键入两次wx,一次是作为包的前缀,另一次是作为通常的前缀,例如wx.wxWindow。
对 于导入顺序需要注意的是:你从wxPython导入其它东西之前必须先导入wx。通常情况下,Python中的模块导入顺序无关。但是wxPython中 的不同,它是一个复杂的模块。当你第一次导入wx模块时,wxPython要对别的wxPython模块执行一些初始化工作。例如wxPython中的一 些子包,如xrc模块,它在wx模块导入之前不能够正确的工作,我们必须按下面顺序导入:
import wx
from wx import xrc
以上的导入顺序只针对wxPython的模块,Python的模块导入顺序没关系。例如:
import sys
import wx
import os
from wx import xrc
import urllib
1.2.2 使用应用程序和框架工作
一旦你导入了wx模块,你就能够创建你的应用程序 (application)对象和框架(frame)对象。每个wxPython程序必须有一个application对象和至少一个frame对象。 application对象必须是wx.App的一个实例或你在OnInit()方法中定义的一个子类的一个实例。当你的应用程序启动的时候, OnInit()方法将被wx.App父类调用。
子类化wxPython application类
下面的代码演示了如何定义我们的wx.App的子类:
class MyApp(wx.App):
def OnInit(self):
frame = wx.Frame(parent=None, id=-1, title="Bare")
frame.Show()
return True
上面我们定义了一个名为MyApp的子类。我们通常在OnInit()方法中创建frame对象。上面的wx.Frame接受三个参数,仅第一个是必须的,其余的都有默认值。
调用Show()方法使frame可见,否则不可见。我们可以通过给Show()一个布尔值参数来设定frame的可见性:
frame.Show(False) # 使框架不可见.
frame.Show(True) # True是默认值,使框架可见.
frame.Hide() # 等同于frame.Show(False)
定义一个应用程序的初始化方法
注 意:我们没有为我们的应用程序类定义一个__init__()方法。在Python中,这就意味着父方法wx.App.__init()__将在对象创建 时被自动调用。这是一个好的事情。如果你定义你自己的__init__()方法,不要忘了调用其基类的__init()__方法,示例如下:
class App(wx.App):
def __init__(self):
wx.App.__init__(self)
如果你忘了这样做,wxPython不将被初始化并且你的OnInit()方法也不将得到调用。
创建一个应用程序实例并进入它的主事件循环
这步是创建wx.App子类的实例,并调用它的MainLoop()方法:
app = App()
app.MainLoop()
一旦进入主事件循环,控制权将转交给wxPython。wxPython GUI程序主要响应用户的鼠标和键盘事件。当一个应用程序的所有框架被关闭后,这个app.MainLoop()方法将返回且程序退出.
1.3 扩展这个最小的空的wxPython程序
现在我们将给空的最小程序增加适当数量的功能,它包含了通常Python编程的标准并能够作为你自己的程
序的一个基准。下面我们创建一个名为spare.py程序:
#!/usr/bin/env python #1
"""Spare.py is a starting point for a wxPython program.""" #2
import wx
class Frame(wx.Frame): #3
pass
class App(wx.App):
def OnInit(self):
self.frame = Frame(parent=None, title='Spare') #4
self.frame.Show()
self.SetTopWindow(self.frame) #5
return True
if __name__ == '__main__': #6
app = App()
app.MainLoop()
这个程序仍然很小,只有14行代码,但是它增加了几个重要的项目让我们考虑到什么样的代码是好的,完
整的。
#1 这行看似注释,但是在如linux和unix等操作系统上,它告诉操作系统如何找到执行程序的解释器。如
果这个程序被给予的可执行权限(例如使用chmod命令),我们可以在命令行下仅仅键入该程序的名字来
运行这个程序:
% spare.py
这行在其它的操作系统上将被忽略。但是包含它可以实现代码的跨平台。
#2 这是文档字符串,当模块中的第一句是字符串的时候,这个字符串就成了该模块的文档字符串并存储
在该模块的__doc__属性中。你能够在你的代码中、某些开发平台、甚至交互模式下运行的Python解释器
中访问文档字符串:
>>> import spare
>>> print spare.__doc__
Spare.py is a starting point for simple wxPython programs.
>>>
#3 我们改变了你们创建frame对象的方法。bare版的程序简单地创建了一个wx.Frame类的实例。在spare
版中,我们定义了我们自己的Frame类作为wx.Frame的子类。此时,最终的结果没有什么不同,但是如果
你想在你的框架中显示诸如文本、按钮、菜单的话,你可能就想要你自己的Frame类了。
#4 我们将对frame实例的引用作为应用程序实例的一个属性
#5 在OnInit()方法中,我们调用了这个App类自己的SetTopWindow()方法,并传递给它我们新创建的
frame实例。我们不必定义SetTopWindow()方法,因为它继承自wx.App父类。SetTopWindow()方法是一个
可选的方法,它让wxPython方法知道哪个框架或对话框将被认为是主要的。一个wxPython程序可以有几个框架
,其中有一个是被设计为应用程序的顶级窗口的。
#6 这个是Python中通常用来测试该模块是作为程序独立运行还是被另一模块所导入。我们通过检查该模
块的__name__属性来实现:
if __name__ == '__main__':
app = App()
app.MainLoop()
1.4 创建最终的hello.py程序
代码如下:
#!/usr/bin/env python
"""Hello, wxPython! program."""
import wx
class Frame(wx.Frame): #2 wx.Frame子类
"""Frame class that displays an image."""
def __init__(self, image, parent=None, id=-1,
pos=wx.DefaultPosition,
title='Hello, wxPython!'): #3图像参数
"""Create a Frame instance and display image."""
#4 显示图像
temp = image.ConvertToBitmap()
size = temp.GetWidth(), temp.GetHeight()
wx.Frame.__init__(self, parent, id, title, pos, size)
self.bmp = wx.StaticBitmap(parent=self, bitmap=temp)
class App(wx.App): #5 wx.App子类
"""Application class."""
def OnInit(self):
#6 图像处理
image = wx.Image('wxPython.jpg', wx.BITMAP_TYPE_JPEG)
self.frame = Frame(image)
self.frame.Show()
self.SetTopWindow(self.frame)
return True
def main(): #7
app = App()
app.MainLoop()
if __name__ == '__main__':
main()
说明:
#2 定义一个wx.Frame的子类,以便我们更容量控制框架的内容和外观。
#3 给我们的框架的构造器增加一个图像参数。这个值通过我们的应用程序类在创建一个框架的实例时提供。同样,我们可以传递必要的值给wx.Frame.__init__()
#4 我们将用wx.StaticBitmap控件来显示这个图像,它要求一个位图。所以我们转换图像到位图。我们也使用图像的宽度和高度创建一个size元组。这个size元组被提供给wx.Frame.__init__()调用,以便于框架的尺寸匹配位图尺寸。
#5 定义一个带有OnInit()方法的wx.App的子类,这是wxPython应用程序最基本的要求。
#6 我们使用与hello.py在同一目录下的名为wxPython.jpg的文件创建了一个图像对象。
#7 main()函数创建一个应用程序的实例并启动wxPython的事件循环。