ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 第二章 给wxPython程序一个坚实的基础 1. [给你的wxPython程序一个稳固的基础](#A.2BftlPYHaE-wxPython.2Begtej04ATip6M1b6doRX.2BnhA-) 1. [关于所要求的对象我们需要知道些什么?](#A.2BUXNOjmJAiYFsQnaEW.2FmMYWIRTuyXAImBd.2BWQU06bTsBOSP8f-) 2. [如何创建和使用一个应用程序对象?](#A.2BWYJPVVIbXvpUjE9.2FdShOAE4qXpR1KHoLXo9b.2BYxh.2Fx8-) 1. [创建一个wx.App的子类](#A.2BUhte.2Bk4ATio-wx.App.2BdoRbUHx7-) 2. [理解应用程序对象的生命周期](#A.2BdAaJ416UdSh6C16PW.2FmMYXaEdR9UfVRoZx8-) 3. [如何定向wxPython程序的输出?](#A.2BWYJPVVuaVBE-wxPython.2Begtej3aEj5NR.2Bg.3F) 1. [重定向输出](#A.2Bkc1bmlQRj5NR.2Bg-) 2. [修改默认的重定向行为](#A.2BT.2B5lOZ7Yi6R2hJHNW5pUEYhMTjo-) 4. [如何关闭wxPython应用程序?](#A.2BWYJPVVFzle0-wxPython.2BXpR1KHoLXo8.3F) 1. [管理正常的关闭](#A.2Be6F0BmtjXjh2hFFzle0-) 2. [管理紧急关闭](#A.2Be6F0Bn0nYCVRc5Xt-) 5. [如何创建和使用顶级窗口对象?](#A.2BWYJPVVIbXvpUjE9.2FdSiYdn6nepdT41v5jGE.3F) 1. [使用wx.Frame](#A.2BT391KA-wx.Frame) 2. [使用wxPython的ID](#A.2BT391KA-wxPython.2BdoQ-ID) 3. [使用wx.Size和wx.Point](#A.2BT391KA-wx.Size.2BVIw-wx.Point) 4. [使用wx.Frame的样式](#A.2BT391KA-wx.Frame.2BdoRoN18P-) 6. [如何为一个框架增加对象和子窗口?](#A.2BWYJPVU46TgBOKmhGZ7ZYnlKgW.2FmMYVSMW1B6l1Pj.3F) 1. [给框架增加窗口部件](#A.2BftloRme2WJ5SoHqXU.2BOQ6E72-) 2. [给框架增加菜单栏、工具栏和状态栏。](#A.2BftloRme2WJ5SoIPcU1VoDzABXeVRd2gPVIxytmABaA8wAg-) 7. [如何使用一般的对话框?](#A.2BWYJPVU9.2FdShOAIIsdoRb.2BYvdaEY.3F) 8. [一些最常见的错误现象及解决方法?](#A.2BTgBOm2cAXjiJwXaElRmL73OwjGFTyonjUbNluWzV.3F) 9. [总结](#A.2BYDt.2B0w-) 房屋的基础是混凝土结构,它为其余的建造提供了坚固的基础。你的`wxPython`程序同样有一个基础,它由两个必要的对象组成,用于支持你的应用程序的其余部分。它们是应用程序对象和顶级窗口对象。适当地使用这两个对象将给你的`wxPython`应用程序一个稳固的开始并使得构造你的应用程序的其余部分更容易。 ## 关于所要求的对象我们需要知道些什么? 让我们来说明一下这两个基础对象。**应用程序对象**管理主事件循环,主事件循环是你的`wxPython`程序的动力。启动主事件循环是应用程序对象的工作。没有应用程序对象,你的`wxPython`应用程序将不能运行。 **顶级窗口**通常管理最重要的数据,控制并呈现给用户。例如,在词处理程序中,主窗口是文档的显示部分,并很可能管理着该文档的一些数据。类似地,你的`web`浏览器的主窗口同时显示你所关注的页面并把该页作为一个数据对象管理。 下图显示了这两个基础对象和你的应用程序的其它部分这间的关系: ![](https://box.kancloud.cn/2016-08-21_57b99606d2b89.gif) 如图所示,这个应用程序对象拥有顶级窗口和主事件循环。顶级窗口管理其窗口中的组件和其它的你分配给它的数据对象。窗口和它的组件的触发事件基于用户的动作,并接受事件通知以便改变显示。 ## 如何创建和使用一个应用程序对象? 任何`wxPython`应用程序都需要一个应用程序对象。这个应用程序对象必须是类`wx.App`或其定制的子类的一个实例。应用程序对象的主要目的是管理幕后的主事件循环。这个事件循环响应于窗口系统事件并分配它们给适当的事件处理器。这个应用程序对象对`wxPython`进程的管理如此的重要以至于在你的程序没有实例化一个应用程序对象之前你不能创建任何的`wxPython`图形对象。 父类`wx.App`也定义了一些属性,它们对整个应用程序是全局性的。很多时候,它们就是你对你的应用程序对象所需要的全部东西。假如你需要去管理另外的全局数据或连接(如一个数据库连接),你可以定制应用程序子类。在某些情况下,你可能想为专门的错误或事件处理而扩展这个主事件循环。然而,默认的事件循环几乎适合所有的你所要写的`wxPython`应用程序。 ### 创建一个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`,且不需要任何其它的全局参数。 ### 理解应用程序对象的生命周期 你的`wxPython`应用程序对象的生命周期开始于应用程序实例被创建时,在最后一个应用程序窗口被关闭时结束。这个没有必要与你的`wxPython`应用程序所在的`Python`脚本的开始和结束相对应。`Python`脚本可以在`wxPython`应用程序创建之前选择做一动作,并可以在`wxPython`应用程序的`MainLoop()`退出后做一些清理工作。然而所有的`wxPython`动作必须在应用程序对象的生命周期中执行。正如我们曾提到过的,这意味你的主框架对象在`wx.App`对象被创建之前不能被创建。(这就是为什么我们建议在`OnInit()`方法中创建顶级框架——因为这样一来,就确保了这个应用程序已经存在。) 下图所示,创建应用程序对象触发`OnInit()`方法并允许新的窗口对象被创建。在`OnInit()`之后,这个脚本调用`MainLoop()`方法,通知`wxPython`事件现在正在被处理。在窗口被关闭之前应用程序继续它的事件处理。当所有顶级窗口被关闭后,`MainLoop()`函数返回同时应用程序对象被注销。这之后,这个脚本能够关闭其它的可能存丰的连接或线程。 ![](https://box.kancloud.cn/2016-08-21_57b99606eb304.gif) ## 如何定向wxPython程序的输出? 所有的`Python`程序都能够通过两种标准流来输出文本:分别是标准输出流`sys.stdout`和标准错误流`sys.stderr`。通常,`Python`脚本定向标准输出流到它所运行的控制台。然而,当你的应用程序对象被创建时,你可以决定使用`wxPython`控制标准流并重定向输出到一个窗口。在`Windows`下,这个重定向行为是`wxPython`的默认行为。而在`Unix`系统中,默认情况下,`wxPython`不控制这个标准流。在所有的系统中,当应用程序对象被创建的时候,重定向行为可以被明确地指定。我们推荐利用这个特性并总是指定重定向行为来避免不同平台上的不同行为产生的任何问题。 ### 重定向输出 如果`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** 运行的时候,应用程序创建了一个空的框架和也生成了一个用于重定向输出的框架。图示如下: ![](https://box.kancloud.cn/2016-08-21_57b996070d63e.gif) 注意:`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`的框架中,但窗口消失太快,所以无法被屏幕捕获。 ### 修改默认的重定向行为 为了修改这个行为,`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`对象控制流的时期之外。 ## 如何关闭wxPython应用程序? 当你的应用程序的最后的顶级窗口被用户关闭时,`wxPython`应用程序就退出了。我们这里所说的顶层窗口是指任何没有父亲的框架,并不只是使用`SetTopWindow()`方法设计的框架。这包括任何由`wxPython`自身创建的框架。在我们重定向的例子中,`wxPython`应用程序在主框架和输出重定向的框架都被关闭后退出,仅管只有主框架是使用`SetTopWindow()`登记的,尽管应用程序没有明确地创建这个输出重定向框架。要使用编程触发一个关闭,你可以在所有的这里所谓顶级窗口上调用`Close()`方法。 ### 管理正常的关闭 在关闭的过程期间,`wxPython`关心的是删除所有的它的窗口和释放它们的资源。你可以在退出过程中定义一个钩子来执行你自己的清理工作。由于你的`wx.App`子类的`OnExit()`方法在最后一个窗口被关闭后且在`wxPython`的内在的清理过程之前被调用,你可以使用`OnExit()`方法来清理你创建的任何非`wxPython`资源(例如一个数据库连接)。即使使用了`wx.Exit()`来关闭`wxPython`程序,`OnExit()`方法仍将被触发。 如果由于某种原因你想在最后的窗口被关闭后`wxPython`应用程序仍然可以继续,你可以使用`wx.App`的`SetExitOnFrameDelete(flag)`方法来改变默认的行为。如果`flag`参数设置为`False`,那么最后的窗口被关闭后`wxPython`应用程序仍然会继续运行。这意味着`wx.App`实例将继续存活,并且事件循环将继续处理事件,比如这时你还可以创建所有新的这里所谓的顶级窗口。`wxPython`应用程序将保持存活直到全局函数`wx.Exit()`被明确地调用。 ### 管理紧急关闭 你不能总是以一个可控的方法关闭你的程序。有时候,你需要立即结束应用程序并不考虑清理工作。例如一个必不可少的资源可能已被关闭或被损坏。如果系统正在关闭,你可能不能做所有的清理工作。 这里有两种在紧急情况下退出你的`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`,那么应用程序将试图否决关闭。 ## 如何创建和使用顶级窗口对象? 在你的应用程序中一个顶级窗口对象是一个窗口部件(通常是一个框架),它不被别的窗口部件所包含。顶级窗口对象通常是你的应用程序的主窗口,它包含用户与之交互的窗口部件和界面对象。当所有的顶级窗口被关闭时应用程序退出。 你的应用程序至少必须有一个顶级窗口对象。顶级窗口对象通常是类`wx.Frame`的子类,尽管它也可以是`wx.Dialog`的子类。大多数情况下,你将为了使用为你的应用程序定义定制的`wx.Frame`的子类。然而,这儿也存在一定数量的预定义的`wx.Dialog`的子类,它们提供了许多你可能会在一个应用程序中遇到的典型的对话框。 这儿可能有一个名称上的混淆,那就是“顶级窗口”。一般意义上的顶级窗口是指在你的应用程序中任何没有父容器的窗口部件。你的应用程序必须至少有一个,但是,只要你喜欢可以有多个。但是它们中只有一个可以通过使用`SetTopWindow()`被`wxPython`作为主顶级窗口。如果你没有使用`SetTopWindow()`指定主顶级窗口,那么在`wx.App`的顶级窗口列表中的第一个框架将被认为是这个主顶级窗口。因此,明确地定义一个主顶级窗口不总是必要的,例如,你只有一个顶级窗口的时候。反复调用`SetTopWindow()`将反复改变当前的主顶级窗口,因为一个应用程序一次只能有一主顶级窗口。 ### 使用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)) ``` ### 使用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() ``` ### 使用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)) ``` ### 使用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`样式是否被应用。 下面的四张图显示了几个通常的框架的样式。 ![](https://box.kancloud.cn/2016-08-21_57b99607218e5.gif) ![](https://box.kancloud.cn/2016-08-21_57b99607336db.gif) ![](https://box.kancloud.cn/2016-08-21_57b996074801c.gif) ![](https://box.kancloud.cn/2016-08-21_57b996075c649.gif) 图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`。 ## 如何为一个框架增加对象和子窗口? 我们已经说明了如何创建`wx.Frame`对象,但是创建后的是空的。本节我们将介绍在你的框架中插入对象与子窗口的基础,以便与用户交互。 ### 给框架增加窗口部件 图2.8显示了一个定制的`wx.Frame`的子类,名为`InsertFrame`。当点击`close`按钮时,这个窗口将关闭且应用程序将退出。例2.3定义了子类`InsertFrame`。 ![](https://box.kancloud.cn/2016-08-21_57b9960774bdd.gif) 例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.9显示了一个有菜单栏、工具栏和状态栏的框架。 ![](https://box.kancloud.cn/2016-08-21_57b9960788d30.gif) 例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', 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, " ") menu2 = wx.Menu() #6 创建菜单的项目 menu2.Append(wx.NewId(), " ", "Copy in status bar") menu2.Append(wx.NewId(), "C ", "") menu2.Append(wx.NewId(), "Paste", "") menu2.AppendSeparator() menu2.Append(wx.NewId(), " ", "Display Options") menuBar.Append(menu2, " ") # 在菜单栏上附上菜单 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`,选项的文本,当鼠标位于其上时显示在状态栏的文本。 ## 如何使用一般的对话框? `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所示: ![](https://box.kancloud.cn/2016-08-21_57b996079c440.gif) `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显示了上面这个对话框: ![](https://box.kancloud.cn/2016-08-21_57b99607b5902.gif) 上面的`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显示了上面代码片断的结果。 ![](https://box.kancloud.cn/2016-08-21_57b99607c9035.gif) `wx.SingleChoiceDialog`的参数类似于文本输入对话框,只是以字符串的列表代替了默认的字符串文本。要得到所选择的结果有两种方法,`GetSelection()`方法返回用户选项的索引,而`GetStringSelection()`返回实际所选的字符串。 ## 一些最常见的错误现象及解决方法? 有一些错误它们可能会发生在你的`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`关闭重定向选项,以便看到错误提示。 ## 总结 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. 尽管你将使用框架做任何复杂的事情,但当你想简单而快速地得到来自用户的信息时,你可以给用户显示一个标准的对话窗口。对于很多任务都有标准的对话框,包括警告框、简单的文本输入框和列表选择框等等。