<> = 用PyCrust使得wxPython更易处理 = {{{PyCrust}}}是一个图形化的{{{shell}}}程序,使用{{{wxPython}}}写成,它可以用来帮助你分析你的{{{wxPython}}}程序。 为何称它为{{{PyCrust}}}?这是因为当{{{Patrick O’Brien}}}使用{{{wxPython}}}创建一个交互式的{{{Python}}} {{{shell}}}时,{{{PyShell}}}已被使用了,所以选用了{{{PyCrust}}}这个名字。 {{{PyCrust}}}是{{{Py}}}包中的一部分,{{{Py}}}包目前被包含在{{{wxPython}}}中。这个{{{Py}}}包还包含了其它相关功能的程序,这包括{{{PyFilling}}}, {{{PyAlaMode}}}, {{{PyAlaCarte}}}, 和{{{PyShell}}}。这些程序每个都是想成为融图形化、点击环境 、{{{wxPython}}}的交互、内省运行特点为一体。但是{{{PyCrust}}}表现最完善。 在这章中,我们将说明{{{PyCrust}}}和那些相关程序都干些什么,还有,你如何使用它们才能使得你用{{{wxPython}}}工作得更流畅。我们以谈论普通的{{{Python}}} {{{shell}}}作为开始,然后专门针对{{{PyCrust}}},最后我们将涉及{{{Py}}}包中剩下的程序。 == 如何与wxPython程序交互? == 与其它编程语言相比,{{{Python}}}的一个显著的特点是你可以以两种方式来使用它:你可以用它来运行存在的使用{{{Python}}}语言写的程序,或从命令提示符来交互地运行{{{Python}}}。交互地运行{{{Python}}}如同与{{{Python}}}解释器会话。 在下例4.1中,我们从命令行启动{{{Python}}},并键入一些数学运算。{{{Python}}}启动后显示几行信息,然后是它的主提示符' '。当你键入的东西要求额外的代码行时,{{{Python}}}显示它的次提示符'...'。 例4.1 简单的{{{Python}}}交互式会话 {{{ $ Python Python 2.3.3 (#1, Jan 25 2004, 11:06:18) [GCC 3.2.2 (Mandrake Linux 9.1 3.2.2-3mdk)] on linux2 Type "help", "copyright", "credits" o "license" for more information. 2 + 2 4 7 * 6 42 5 ** 3 125 for n in range(5): ... print n * 9 ... 0 9 18 27 36 }}} 交互式的{{{Python}}}不仅仅是一个好的桌面计算器,它也是一个好的学习工具,因为它提供了及时的反馈。当你有疑问时,你可以运行{{{Python}}},键入几行试验性的代码,看{{{Python}}}如何反应,据此调整你的主要代码。学习{{{Python}}}或学习现有的{{{Python}}}代码是如何工作的,最好的方法之一就是交互式地调试。 '''{{{PyCrust}}}配置了标准的{{{Python}}}''' '''{{{shell}}}''' 当你交互式的使用{{{Python}}}工作时,你工作在一个称为{{{Python}}} {{{shell}}}的环境中,它类似于其它的{{{shell}}}环境,如微软平台的{{{DOS}}}窗口,或类{{{Unix}}}系统的{{{bash}}}命令行。 所有{{{Python}}} {{{shell}}}中最基本的是例4.1中所出现的,它是你从命令行启动{{{Python}}}时所见到的。虽然它是一个有用的{{{shell}}},但是它基于文本的,而非图形化的,并且它不提供快捷方式或有帮助的提示。有几个图形化的{{{Python}}} {{{shell}}}已经被开发出来了,它们提供了这些额外的功能。最著名的是{{{IDLE}}},它是{{{Python}}}发布版的标准部分。{{{IDLE}}}如下图4.1所示: {{attachment:w4.1.gif}} {{{IDLE}}}看起来很像命令行{{{Python}}} {{{shell}}},但是它有额外的特性如调用提示。 其它的{{{Python}}}开发工具,如{{{PythonWin}}}和{{{Boa}}} {{{Constructor}}},包括了类似于{{{IDLE}}}中的图形化{{{Python}}} {{{shell}}}。虽然每种工具的{{{shell}}}各有一些有用的特性,如命令再调用({{{recall)}}}、自动完成、调用提示,但是没有一个工具完整包含了所有的特性 。在这种情况下,{{{PyCrust}}}产生了,{{{PyCrust}}}的目的之一就是提供所有现存的{{{Python}}} {{{shell}}}的特性。 创建{{{PyCrust}}}的另一个动机是:使用一个{{{GUI}}}工具包所写的代码不能工作在另一个不同的{{{GUI}}}工具包上。例如,{{{IDLE}}}是用{{{Tkinter}}}写的,而不是{{{wxPython}}}。由于这样,如果你试图在{{{IDLE}}}的{{{Python}}} {{{shell}}}中引入和使用{{{wxPython}}}模块,那么你将陷入{{{wxPython}}}的事件循环与{{{Tkinter}}}事件循环间的冲突,结果将导致程序的冻结或崩溃。 事实上,这两个工具包将在控制事件循环上产生冲突。因此,如果你使用{{{wxPython}}}模块工作时想要内省运行特性,你的{{{Python}}} {{{shell}}}必须是用{{{wxPython}}}写的。由于没有现存的{{{Python}}} {{{shell}}}支持完整的特性,{{{PyCrust}}}被创建来填补这种需要。 == PyCrust的有用特性是什么? == 现在,我们将关注{{{PyCrust}}}提供的{{{shell}}}的一些特性。{{{PyCrust}}}的{{{shell}}}看起来有些熟悉,这是因为它也显示了如同命令行{{{Python}}} {{{shell}}}相同的信息行和提示符。下图4.2显示了一个打开着的{{{PyCrust}}}的屏幕: {{attachment:w4.2.gif}} 你应该注意一下这个{{{PyCrust}}}框架,它包含了一个{{{wx.SplitterWindow}}}控件,框架被分成两个区域:上部的区域看起来像通常的{{{Python}}} {{{shell}}};底部的区域包含了一个{{{Notebook}}}控件,这个控件包含了不同的标签,默认标签显示的信息是有关当前的名字空间的。上部区域是{{{PyCrust}}} {{{shell}}},它有几个有用的特性,我们将在下面几节讨论。 === 自动完成 === 当你在一个对象名后键入一点号时将引发自动完成功能。{{{PyCrust}}}将按字母顺序显示关于该对象的所有已知的属性的一个列表。当你在点号后输入字母时,在列表中的高亮选项将改变去匹配你所输入的字母。如果高亮选项正是你所要的,这时按下{{{Tab}}}键,{{{PyCrust}}}将为你补全该属性名的其余部分。 在下图4.3中,{{{PyCrust}}}显示一个字符串对象的属性的列表。这个自动完成的列表包含了该对象的所有属性和方法。 图4.3 {{attachment:w4.3.gif}} === 调用提示和参数默认 === 当你在一个可调用的对象名后键入左括号时,{{{PyCrust}}}显示一个调用提示窗口(如图4.4),该窗口包含了所能提供的参数信息和文档字符串(如果可调用对象中定义了文档字符串的话)。 可调用对象可以是函数、方法、内建的或类。可调用对象的定义都可以有参数,并且可以有用来说明功能的文档字符串,以及返回值的类型。如果你知道如何使用该可调用对象,那么你可以忽略调用提示并继续键入。 图4.4 {{attachment:w4.4.gif}} === 语法高亮 === 当你在{{{shell}}}中键入代码时,{{{PyCrust}}}根据它的重要性改变文本的颜色。例如,{{{Python}}}的关键词用一种颜色显示,原义字符串用另一种颜色,注释用另一种颜色。这就使得你可以通过颜色来确认你的输入是否有误。 === Python === '''帮助''' {{{PyCrust}}}完整地提供了关于{{{Python}}}的帮助功能。{{{Python}}}的帮助功能显示了几乎所有{{{Python}}}方面的信息,如下图4.5所示 图4.5 {{attachment:w4.5.gif}} {{{Python}}}的帮助功能提供了另外一个提示符({{{help)}}}。在使用了{{{help}}}之后,你可以通过在{{{help}}}提示符之后键入{{{quit}}}来退出帮助模式,返回到通常的{{{Python}}}提示符( )。 === 命令重调用 === 在{{{PyCrust}}} {{{shell}}}中有多种方法可以用来减少重复输入。它们大都通过捕获你先前的键入来实现,如果有必要,你可以修改所捕获的内容,之后它们将之发送给{{{Python}}}解释器。 例如,{{{PyCrust}}}维护着当前会话中你所键入的所有命令的一个历史记录。你可以从命令历史记录中重调用你先前键入的任何{{{Python}}}命令(一行或多行)。下表4.1显示了一个关于该功能的快捷键列表。 {{{Ctrl}}}+上箭头:获取前一个历史项 {{{Alt}}}+P:获取前一个历史项 {{{Ctrl}}}+下箭头:获取下一个历史项 {{{Alt}}}+N:获取下一个历史项 {{{Shift}}}+上箭头:插入前一个历史项 {{{Shift}}}+下箭头:插入下一个历史项 {{{F8}}}:历史项命令补全(键入先前命令的少量字符并按{{{F8}}}) {{{Ctrl}}}+{{{Enter}}}:在多行命令中插入新行 正如你所看到的,这儿有不同的命令用于获取和插入旧命令,它们通过{{{PyCrust}}}如何处理当前{{{wxPythob}}}提示符中所键入的文本被区分。要替换你的键入或插入一个旧的命令,可以使用快捷键来获取或插入一个历史项。 插入一行到一个多行命令中的工作与插入到一单行命令不同。要插入一行到一个多行命令,你不能只按{{{Enter}}}键,因为这样将把当前的命令发送给{{{Python}}}解释器。替代的方法是,按下{{{Ctrl}}}+{{{Enter}}}来插入一个中断到当前行。如果你处于行尾,那么一个空行被插入当前行之后。这个过程类似于你在一个通常的文本编辑中剪切和粘帖文本的方法。 最后一种重调用命令的方法是简单地将光标移到想要使用的命令,然后按{{{Enter}}}键。{{{PyCrust}}}复制该命令到当前的{{{Python}}}提示符。然后你可以修改该命令或按{{{Enter}}}键以将该命令提交给解释器。 快捷键让你可以快速地开发代码,并做每步的测试。例如,你可以定义一个新的{{{Python}}}类,创建该类的一个实例,并看它的行为如何。然后,你可以返回到这个类的定义,增加更多的方法或编辑已有的方法,并创建一个新的实例。通过这样的反复,你可以将你的类的定义做得足够好,然后将它粘帖到你的源代码中。 === 剪切和粘贴 === 你可能想重用在{{{shell}}}中已开发的代码,而避免重新键入。有时,你可能找到一些样例代码(可能来自在线的教程),你想把它用到一个{{{Python}}} {{{shell}}}中。{{{PyCrust}}}提供了一些简单的剪切和粘贴选项,列表于下表4.2 {{{Ctrl}}}+C:复制所选的文本,去掉提示符 {{{Ctrl}}}+{{{Shift}}}+C:复制所选的文本,保留提示符 {{{Ctrl}}}+X:剪切所选的文本 {{{Ctrl}}}+V:粘贴自剪贴板 {{{Ctrl}}}+{{{Shift}}}+V:粘贴自剪贴板的多个命令并运行 粘贴的另一个特性是:{{{PyCrust}}}从所粘贴到{{{PyCrust}}} {{{shell}}}中的代码中识别并自动去掉标准的{{{Python}}}提示符。这使得复制教程或{{{email}}}信息中的例子代码,把它粘贴到{{{PyCrust}}}中,并测试它变得简单了,省去了手工的清理。 某些时候,当你复制代码时,你可能想去除{{{PyCrust}}}提示符,如当你复制代码到你的源文件中时。另一些时候,你可能想保留这个提示符,如录你复制例子到一个文档中,或把它发送到一个新闻组。当从{{{shell}}}复制时,{{{PyCrust}}}对这两种情况都提供了支持。 === 标准shell环境 === 在{{{wxPython}}}环境中,{{{PyCrust}}}的行为尽可能地与命令行的{{{Python}}} {{{shell}}}相同。不同的是,一旦{{{Python}}}代码被输入到了{{{PyCrust}}} {{{shell}}}中,就没有办法来中断该代码的运行。例如,假定你在{{{PyCrust}}}中写了一个无限循环,如下所示: {{{while}}} {{{True:}}} ... {{{print}}} "{{{Hello}}}" ... 在你按下{{{Enter}}}之后,上面的代码被传送到{{{Python}}}解释器,{{{PyCrust}}}停止响应。要中断这个无限的循环,必须关闭{{{PyCrust}}}程序。这个缺点是与命令行的{{{Python}}} {{{shell}}}对比而言的。命令行的{{{Python}}} {{{shell}}}保留了处理键盘中断({{{Ctrl}}}+C)的能力。在命令行的{{{Python}}} {{{shell}}}中你会看到如下的行为: {{{ while True: ... print "Hello" ... Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Traceback (most recent call last): File " stdin ", line 2, in ? KeyboardInterrupt }}} 在{{{GUI}}}环境中的事件处理的本质,使得设计出能够让{{{PyCrust}}}中断一个无限循环或在{{{shell}}}提示符中键入的长时间运行的代码序列的方法有很大的不同。将来的{{{PyCrust}}}版本可能会提供对这个缺点的一个解决办法。幸运的是,在{{{PyCrust}}}和标准命令{{{shell}}}之间只有这一个不同点。在其它方面,{{{PyCrust}}} {{{shell}}}和命令行的{{{Python}}} {{{shell}}}工作的完全一样。 === 动态更新 === 当你在运行{{{PyCrust}}}时,{{{PyCrust}}}的{{{shell}}}的所有特性都是动态地被更新的,这意味着,诸如“自动完成”和“调用提示”等特性是有效的,即使是在{{{shell}}}提示符中定义的对象。例如图4.6和4.7所显示的会话,那么我们定义并使用了一个类。 在图4.6中,{{{PyCrust}}}为新类显示了自动完成选项。 图4.6 {{attachment:w4.6.gif}} 在图4.7中,{{{PyCrust}}}显示了关于类所定义的新的方法的调用提示。 图4.7 {{attachment:w4.7.gif}} == PyCrust == '''{{{notebook}}}的标签是干什么的?''' {{{PyCrust}}}界面的下半部是一个{{{notebook}}}控件,{{{notebook}}}控件包括了几个带有有用信息的标签。{{{PyCrust}}}开始时,你所看到的标签是“{{{Namespace}}}”标签。 === Namespace标签 === 如图4.8所示,{{{Namespace}}}标签又被用{{{wx.SplitterWindow}}}控件分成两部分。左边包含一个树控件,它显示当前的名字空间,而右边显示在名字空间树中当前被选择的对象的细节。 图4.8 {{attachment:w4.8.gif}} 名字空间树呈现一个关于在当前名字空间中所有对象的层次关系的视图。如果你运行{{{Python}}}的内建函数{{{locals()}}},这些对象将作为返回结果。在图4.8中,我们已经导入了{{{wx}}}包并在名字空间树中选择了它。右边显示了所选择的项目的名字,它的类型和它的当前值。如果对象有与之相关联的源代码,{{{PyCrust}}}也将显示出来。这里,{{{wx}}}是一个{{{wxPython}}}包,所以{{{PyCrust}}}显示{{{__init__.py}}}文件的源代码,该文件位于{{{wx}}}目录中。 右边显示的第一行是左边所选择的对象的全名,你可以把它复制并粘贴到{{{PyCrust}}} {{{shell}}}或你的应用程序源码中。例如,我们在{{{PyCrust}}}中引入{{{locale}}}模块并选择名字空间树中{{{locale}}}/{{{encoding_alias}}}/'{{{en}}}'项,右边就显示了所选对象的完整名,你可以把它复制并粘贴到{{{PyCrust}}} {{{shell}}}中,如下所示: {{{#!python import locale locale.encoding_alias['en'] 'ISO8859-1' }}} 这里,{{{PyCrust}}}给我们提供了一个全名( {{{locale.encoding_alias}}}['{{{en}}}']),它使用{{{Python}}}的索引(['{{{en}}}'])来引用{{{encoding_alias}}}目录中的指定项目。这个机制同样适用于列表({{{list)}}}。如果你在名字空间树中发现了你想用在你的代码中的东西,那么{{{PyCrust}}}给了你这精确语法去完成这个任务。 === Display标签 === {{{Display}}}标签中用于显示一个对象。{{{PyCrust}}}有一个内建函数{{{pp()}}},这个函数使用{{{Python}}}的{{{pprint}}}模块为显示一个对象。使用中不需要显式地引入和重复使用{{{pprint}}},在{{{Display}}}中,这些信息随对象的更新而每次更新。 例如,如果我们在{{{PyCrust}}} {{{shell}}}中有一个列表,我们要在 {{{Display}}}标签中显示它的内容,我们可以在{{{PyCrust}}} {{{shell}}}中使用{{{pp()}}},然后列表的内容就显示在 {{{Display}}}标签中了。以后每当我们改变了列表的内容, {{{Display}}}标签中的内容随即改变。 {{{Calltip}}}标签显示了在{{{Python}}} {{{shell}}}中最近调用提示的内容。如果你的调用要求大量的参数,那么你可以选择{{{Calltip}}}标签。当使用{{{wxPython}}}包时,存在着大量的类,这些类有许多方法,这些方法又要求许多参数。例如,为了创建一人{{{wx.Button}}},你可能要提供八个参数,有一个是必须提供的,其它七个有默认的值。{{{Calltip}}}标签显示了关于{{{wx.Button}}}构造器的细节,如下所示: {{{#!python __init__(self, Window parent, int id=-1, String label=EmptyString, Point pos=DefaultPosition, Size size=DefaultSize, long style=0, Validator validator=DefaultValidator, String name=ButtonNameStr) - Button Create and show a button. The preferred way to create standard buttons is to use a standard ID and an empty label. In this case wxWigets will automatically use a stock label that corresponds to the ID given. In addition, the button will be decorated with stock icons under GTK+2. }}} 由于{{{wxPython}}}的类实际上是封装的C++的类,所以调用提示信息完全基于类的文档字符串。它们显示了底层C++类所需要的参数和类型信息。对于完全用{{{Python}}}语言定义的对象,{{{PyCrust}}}检查它们以确定它的参数特性。 === Session标签 === {{{Session}}}标签是一个简单的文本控件,它列出了在当前{{{shell}}}会话中所键入的所有命令。这使得剪切和粘贴命令以用在别处更为简单。 === Dispatcher === '''标签''' {{{PyCrust}}}包括了一个名为{{{dispatcher}}}的模块,它提供了在一个应用程序中联系对象的机制。{{{PyCrust}}}使用{{{dispatcher}}}来维持它的界面的更新,主要是在命令从{{{shell}}}传送到{{{Python}}}解释器时。图4.9中的{{{Dispatcher}}}标签列出了关于信号经过分配机制后的路由。当使用{{{PyCrust}}}工作时,这是它的主要用处。 图4.9 {{attachment:w4.9.gif}} 这里的{{{Dispatcher}}}标签也演示了如何增加另一个标签到一个{{{wx.Notebook}}}控件。下面这个在{{{Dispatcher}}}标签上的文本控件的源码,演示了如何使用{{{dispatcher}}}模块: {{{#!python class DispatcherListing(wx.TextCtrl): """Text control containing all dispatches for session.""" def __init__(self, parent=None, id=-1): style = (wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP) wx.TextCtrl.__init__(self, parent, id, style=style) dispatcher.connect(receiver=self.spy) def spy(self, signal, sender): """Receiver for Any signal from Any sender.""" text = '%r from %s' % (signal, sender) self.SetInsertionPointEnd() start, end = self.GetSelection() if start != end: self.SetSelection(0, 0) self.AppendText(text + '\n') }}} 现在我们已经看到了{{{PyCrust}}}作为独立的{{{Python}}} {{{shell}}}和名子空间检查器能够做些什么,下面让我们关注在你的{{{wxPython}}}程序中,{{{PyCrust}}}的其它一些用法。 == 如何将PyCrust应用于wxPython应用程序 == 。 让我们假设你已经用{{{wxPython}}}创建了一个程序,并且你的程序正在工作,现在你想更好地了解它是如何工作的。在这章的前面你已经看到了{{{PyCrust}}}的特性,它们看起来对于理解你的程序的功能是非常有用的。 通过将你的程序的名字传递给{{{PyWrap}}},你能够用{{{PyCrust}}} {{{shell}}}来启动你的程序,不需要对你的程序作任何的改变。下例4.2显示了一个名为{{{spare.py}}}的程序,我们准备对它使用{{{PyCrust}}}。 例4.2 {{{#!python #!/usr/bin/env python """Spare.py is a starting point for simple wxPython programs.""" import wx class Frame(wx.Frame): pass class App(wx.App): def OnInit(self): self.frame = Frame(parent=None, id=-1, title='Spare') self.frame.Show() self.SetTopWindow(self.frame) return True if __name__ == '__main__': app = App() app.MainLoop() }}} 为了运行这个程序时使用{{{PyCrust}}},要将该程序的全路径传递给{{{PyWrap}}}。在{{{Linux}}}上,命令行类似如下: {{{ $ pywrap spare.py }}} 在{{{windows}}}下,命令行类似如下: {{{ F:\ python pywrap.py spare.py }}} 在开始的时候,{{{PyWrap}}}试图导入命令行所包括的模块。然后{{{PyWrap}}}在模块中寻找{{{wx.App}}}的子类,并创建子类的一个实例。之后,{{{PyWrap}}}创建一个带有{{{shell}}}的{{{wx.py.crust.CrustFrame}}}窗口,把这个应用程序对象显示在{{{PyCrust}}}的名字空间树中,并且启动 {{{wxPython}}}事件循环。 {{{PyWrap}}}的源码显示在例子4.3中。它显示了如何用少量的代码将大量的功能增加到你的程序中。 例4.3 {{{#!python """PyWrap is a command line utility that runs a python program with additional runtime tools, such as PyCrust.""" __author__ = "Patrick K. O'Brien pobrien@orbtech.com " __cvsid__ = "$Id: PyCrust.txt,v 1.15 2005/03/29 23:39:27 robind Exp $" __revision__ = "$Revision: 1.15 $"[11:-2] import os import sys import wx from wx.py.crust import CrustFrame def wrap(app): wx.InitAllImageHandlers() frame = CrustFrame() frame.SetSize((750, 525)) frame.Show(True) frame.shell.interp.locals['app'] = app app.MainLoop() def main(modulename=None): sys.path.insert(0, os.curdir) if not modulename: if len(sys.argv) 2: print "Please specify a module name." raise SystemExit modulename = sys.argv[1] if modulename.endswith('.py'): modulename = modulename[:-3] module = __import__(modulename) # Find the App class. App = None d = module.__dict__ for item in d.keys(): try: if issubclass(d[item], wx.App): App = d[item] except (NameError, TypeError): pass if App is None: print "No App class was found." raise SystemExit app = App() wrap(app) if __name__ == '__main__': main() }}} 运行了{{{PyWrap}}}命令之后,来自{{{spare}}}的简单的框架({{{frame)}}}和{{{PyCrust}}}的框架都显示出来。 '''{{{PyCrust}}}''' '''{{{in}}}''' '''{{{action}}}''' 现在让我们看看,在{{{PyCrust}}} {{{shell}}}中我们对{{{spare.py}}}应用程序框架做些什么。图4.10显示了这个结果。我们将通过导入{{{wx}}}和增加一个画板到我们的框架作为开始: {{{#!python import wx app.frame.panel = wx.Panel(parent=app.frame) app.frame.panel.SetBackgroundColour('White') }}} {{{True}}} 图4.10 {{attachment:w4.10.gif}} 增加到框架的画板开始时是默认的银灰色,然后它被改变到白色。然而,设置画板背景色不立即改变它的显示。这需要去触发一个事件来导致画板重绘,以使用它的新颜色属性。一个触发这样事件的方法是要求画板刷新自身: {{{#!python app.frame.panel.Refresh() }}} 现在一个白色的画板显示了,我们对于理解{{{wxPython}}}如何工作的细节又进了一步。 接下来,让我们增加一个状态栏: {{{#!python app.frame.statusbar = app.frame.CreateStatusBar(number=3) app.frame.statusbar.SetStatusText("Left", 0) app.frame.statusbar.SetStatusText("Center", 1) app.frame.statusbar.SetStatusText("Right", 2) }}} 注意在不改变这个框架的尺寸情况下,这个状态栏在这个框架中是如何显示的。也要注意添加到三个状态栏中的文本的立即显示了出来,而不要求刷新。现在让我们增加一个菜单和一个菜单栏: {{{#!python app.frame.menubar = wx.MenuBar() menu = wx.Menu() app.frame.menubar.Append(menu, "Primary") app.frame.SetMenuBar(app.frame.menubar) menu.Append(wx.NewId(), "One", "First menu item") menu.Append(wx.NewId(), "Two", "Second menu item") }}} 当你在{{{PyCrust}}} {{{shell}}}中处理你自己的{{{wxPython}}}对象时,注意改变对你正在运行的程序的影响。试试回答后面的问题。在框架中菜单何时才实际显示出来的?在程序运行的时候,你能改变菜单的哪些属性?你能够让它们无效吗?交互地探究这些可以帮助你更好的理解{{{wxPython}}},同时当你写真实的代码时给你带来更大的自信。 到目前,我们已经花了很多节讨论{{{PyCrust}}},我们下面准备看一看{{{Py}}}包的其余的东西。 == 在Py包中还有其它什么? == 所有{{{PyCrust}}}中的程序都利用了{{{Py}}}包中的{{{Python}}}模块,诸如{{{shell.py}}},{{{crust.py}}},{{{introspect.py}}}和 {{{interpreter.py}}}。这些程序是用来做{{{PyCrust}}}的建造块,你可以分别或一起使用它们。 {{{PyCrust}}}代表了组装包含在{{{Py}}}包中功能模块的一各方法。{{{PyShell}}}是另一方法,{{{PyAlaMode}}}是第三种。在这 些方法中,它们的底层代码大多数是相同的,只是外包装有所变化而已。因此,你可以把{{{Py}}}当做一个模块 库,你可以随意地在你的程序中的任何地方组装其中的模块,用来显示一个{{{wxPython}}} {{{shell}}}、一个代码编 辑器或运行时内省信息。 在{{{Py}}}包中,提供给用户界面功能的模块和没有这功能的模块有明显的区别。这个区别使得在你的程序中很 容易使用这些模块。以{{{Py}}}开头的模块是终端用户{{{GUI}}}程序,如{{{PyCrust}}},{{{PyShell}}},{{{PyAlaMode}}}和{{{PyAlaCarte}}}。在你的程序中,你不会想导入这些模块。下节说明终端用户模块。 === 使用GUI程序工作 === 下表4.3说明了用户级程序。 {{{PyAlaCarte}}}:简单的源代码编辑器。一次编辑一个文件。 {{{PyAlaMode}}}:多文件源代码编辑器。每个文件分别显示在一个{{{notebook}}}标签中。第一个标签包含一个{{{PyCrust}}}分隔窗口。 {{{PyCrust}}}:合并了{{{wxPython}}} {{{shell}}}和{{{notebook}}}标签,{{{notebook}}}包含一个名字空间树查看器。 {{{PyFilling}}}:简单的名字空间树查看器。这个程序自己不是很有用。它的存在只是作为如何使用底层库的一个例子。 {{{PyShell}}}:简单的{{{wxPython}}} {{{shell}}}界面,没有{{{PyCrust}}}中的{{{notebook}}}。功能上,{{{PyShell}}}中的{{{wxPython}}} {{{shell}}}和{{{PyCrust}}}中的是一样的。 {{{PyWrap}}}:命令行工具,用以运行一个存在的程序和{{{PyCrust}}}框架,让你能够在{{{PyCrust}}} {{{shell}}}中处理这个应用程序。 === 使用支持模块工作 === 支持模块为终端用户提供了基本的功能,可以被导入你的程序中。这些模块是用来创建用户级{{{Py}}}程序的建 造块。下表4.4列出了这些支持模块,它们是{{{Py}}}包的一部分,说明如下: {{{buffer}}}:支持文件编辑。 {{{crust}}}:包含{{{PyCrust}}}应用程序独有的{{{GUI}}}元素。 {{{dispatcher}}}:提供全局信号分派服务。 {{{document}}}:{{{document}}}模块包含一个非常简单的{{{Document}}}类,这个类是一个小的文件类。{{{document}}}跟踪不同的文件属性,如名字和路径,并提供{{{read()}}}和{{{write()}}}方法。{{{Buffer}}}类委托这些低级的读写操作给一个{{{Document}}}实例。 {{{editor}}}:包含所有显示在{{{PyAlaCarte}}}和{{{PyAlaMode}}}程序中的{{{GUI}}}编辑部分。 {{{editwindow}}}:这个{{{editwindow}}}模块包含了一个简单的{{{EditWindow}}}类。这个类继承自{{{wx.stc.StyledTextCtrl}}} ({{{STC)}}},并且提供了{{{Py}}}包中的{{{STC}}}的三种主要用法的所有共同的特性,这三种主要用法是:作为一个{{{Python}}} {{{shell}}},作为一个源代码编辑器和作为一个只读源码显示器。 {{{filling}}}:包含所有的{{{GUI}}}控件,这些{{{GUI}}}控件让用户能够浏览对象名字空间并显示那些对象运行时的信息。 {{{frame}}}:{{{frame}}}模块定义了一个{{{Frame}}}类,这个{{{Frame}}}类是{{{Py}}}包中所有其它{{{frames}}}的基类。菜单项根据当前状态和上下文不断地自我更新。 {{{images}}}:{{{images}}}模块包含被不同{{{Py}}}程序使用的图标。 {{{interpreter}}}:{{{Interpreter}}}类负责提供自动完成列表,调用提示信息等。 {{{introspect}}}:为一些方面提供多种内省类型,像调用提示和命令自动完成。 {{{pseudo}}}:这个模块定义文件类类,文件类允许{{{Interpreter}}}类去重定向{{{stdin}}},{{{stdout}}},{{{stderr}}}。 {{{shell}}}:这个模块包含{{{GUI}}}元素,这些{{{GUI}}}元素定义显示在{{{PyCrust}}},{{{PyShell}}}和{{{PyAlaMode}}}中的{{{Python}}} {{{shell}}}的界面。 {{{version}}}:这个模块包含一个名为{{{VERSION}}}的字符串变量,{{{VERSION}}}代表{{{Py}}}当前的版本。 下面我们讨论更复杂的模块。 '''{{{buffer}}}模块''' {{{buffer}}}模块包含一个{{{Buffer}}}类,这个类支持文件的通常编辑。{{{buffer}}}有一些方法,例如{{{new()}}}, {{{open()}}}, {{{hasChanged()}}}, {{{save()}}},和{{{saveAs()}}}。文件操作基于{{{buffer}}}所委托的{{{Document}}}类的实例,{{{Document}}}类定义在{{{document}}}模块中。文件内容的实际编辑是通过{{{Editor}}}类的一个或多个实例发生的,{{{Editor}}}类定义在 {{{editor}}}模块中。{{{buffer}}}扮演一个在一个或多个编辑器和实际物理文件之间的中间人。 {{{Buffer}}}类的一个独特的手法是每个{{{buffer}}}实例都分配了它自己的{{{Python}}}解释器实例。这个特点使得{{{buffer}}} 能够被用在那些当编辑{{{Python}}}源代码文件时需要提供自动完成,调用提示和其它运行时帮助的应用程序中 。每个{{{buffer}}}解释器都是完全独立的,并且在{{{buffer}}}的{{{updateNamespace()}}}方法被调用时更新。下例4.4显示了{{{updateNamespace()}}}方法的源代码。 例4.4 {{{#!python def updateNamespace(self): """Update the namespace for autocompletion and calltips. Return True #if updated, False if there was an error.""" if not self.interp o not hasattr(self.editor, 'getText'): return False syspath = sys.path sys.path = self.syspath text = self.editor.getText() text = text.replace('\r\n', '\n') text = text.replace('\r', '\n') name = self.modulename o self.name module = imp.new_module(name) newspace = module.__dict__.copy() try: try: code = compile(text, name, 'exec') except: raise try: exec code in newspace except: raise else: # No problems, so update the namespace. self.interp.locals.clear() self.interp.locals.update(newspace) return True finally: sys.path = syspath for m in sys.modules.keys(): if m not in self.modules: del sys.modules[m] }}} 这个方法使用{{{Python}}}内建的{{{compile}}}方法编译编辑器中的文本,然后使用关键词{{{exec}}}来执行。如果编译成 功,将放置若干变量到{{{newspace}}}名字空间中。通过用执行的结果重置解释器的局部名字空间,解释器支持 访问定义在编辑器的{{{buffer}}}中的任何类,方法或变量。 '''{{{crust}}} 模块''' {{{crust}}}模块包含6个{{{GUI}}}元素,它们专门用于{{{PyCrust}}}应用程序的。这最常用的类是{{{CrustFrame}}},它是{{{wx.Frame}}}的子类。如果你再看一下例4.3,你能看到{{{PyWrap}}}程序是如何导入{{{CrustFrame}}}并创建其一个实例的。这是嵌入一个{{{PyCrust}}}框架到你自己的程序中的最简单的方法。如果你想要比一个完整的框架更小的东西,你可以使用下表4.5所列的一个或多个类。 表4.5 {{{Crust}}}:基于{{{wx.SplitterWindow}}}并包含一个{{{shell}}}和带有运行时信息的{{{notebook}}}。 {{{Display}}}:样式文本控件,使用{{{Pretty}}} {{{Print}}}显示一个对象。 {{{Calltip}}}:文本控件,包含最近{{{shell}}}调用帮助。 {{{SessionListing}}}:文本控件,包含关于一个会话的所有命令。 {{{DispatcherListing}}}:文本控件,包含关于一个会话的所有分派。 {{{CrustFrame}}}:一个框架,包含一个{{{Crust}}}分隔窗口。 这些{{{GUI}}}元素可以被用在任何{{{wxPython}}}程序中,以提供有用的可视化内省。 '''{{{dispatcher}}}模块''' {{{dispatcher}}}提供了全局信号分派服务。那意味它扮演着一个中间人,使得对象能够发送和接受消息,而无须知道彼此。所有它们需要知道的是这个正在发送的信号(通常是一个简单的字符串)。一个或多个对象可以要求这个{{{dispatcher}}},当信号已发出时去通知它们,并且一个或多个对象可以告诉这个{{{dispatcher}}}去发送特殊的信号。 下例4.5是关于为什么{{{dispatcher}}}是如此有用的一个例子。因为所有的{{{Py}}}程序都是建造在相同的底层模块之上的,所以{{{PyCrust}}}和{{{PyShell}}}使用几乎相同的代码。这唯一的不同是,{{{PyCrust}}}包括了一个带有额外功能的{{{notebook}}},如名字空间树查看器,当命令被发送到解释器时,名字空间树查看器更新。在一个命令通过解释器时,解释器使用{{{dispatcher}}}发送一个信号: 例4.5 经由{{{dispatcher}}}模块来发送命令的代码 {{{#!python def push(self, command): """Send command to the interpreter to be executed. Because this may be called recursively, we append a new list onto the commandBuffer list and then append commands into that. If the passed in command is part of a multi-line command we keep appending the pieces to the last list in commandBuffer until we have a complete command. If not, we delete that last list.""" command = str(command) # In case the command is unicode. if not self.more: try: del self.commandBuffer[-1] except IndexError: pass if not self.more: self.commandBuffer.append([]) self.commandBuffer[-1].append(command) source = '\n'.join(self.commandBuffer[-1]) more = self.more = self.runsource(source) dispatcher.send(signal='Interpreter.push', sender=self, command=command, more=more, source=source) return more }}} {{{crust}}}中的各有关部分和{{{filling}}}模块在它们的构造器中通过连接到{{{dispatcher}}},自己作为信号的接受器。下例4.6显示了关于出现在{{{PyCrust}}}的{{{Session}}}标签中的{{{SessionListing}}}控件的源码: 例4.6 {{{PyCrust}}} {{{session}}}标签的代码 {{{#!python class SessionListing(wx.TextCtrl): """Text control containing all commands for session.""" def __init__(self, parent=None, id=-1): style = (wx.TE_MULTILINE | wx.TE_READONLY |wx.TE_RICH2 | wx.TE_DONTWRAP) wx.TextCtrl.__init__(self, parent, id, style=style) dispatcher.connect(receiver=self.push,signal='Interpreter.push') def push(self, command, more): """Receiver for Interpreter.push signal.""" if command and not more: self.SetInsertionPointEnd() start, end = self.GetSelection() if start != end: self.SetSelection(0, 0) self.AppendText(command + '\n') }}} 注意{{{SessionListing}}}的接受器({{{push()}}}方法)是如何忽略由解释器发送来的{{{sender}}}和{{{source}}}参数的。{{{dispatcher}}}非常灵活,并且只发送接受器能接受的参数。 '''{{{editor}}}模块''' {{{editor}}}模块包含了出现在{{{PyAlaCarte}}}和{{{PyAlaMode}}}程序中的所有{{{GUI}}}编辑组件。如果你愿意在你的程序中包括一个{{{Python}}}源代码编辑器,那么使用在下表4.6中所说明的类。 这些类可以被使用在任何程序中以提供有用的代码风格编辑功能。 表4.6 定义在{{{editor}}}模块中的类 {{{EditerFrame}}}:被{{{PyAlaCarte}}}用来支持一次一个文件的编辑。{{{EditerFrame}}}是来自{{{frame}}}模块的较一般的{{{Frame}}}类的子类。 {{{EditorNotebookFrame}}}:{{{EditerFrame}}}的子类,它通过增加一个{{{notebook}}}界面和同时编辑多个文件的能力扩展了{{{EditerFrame}}}。它是一个被{{{PyAlaMode}}}使用的{{{frame}}}类。 {{{EditorNotebook}}}:这个控件被{{{EditorNotebookFrame}}}用来在各自的标签中显示各自的文件。 {{{Editor}}}:管理一个{{{buffer}}}和与之相关的{{{EditWindow}}}之间的关联。 {{{EditWindow}}}:基于{{{StyledTextCtrl}}}的文本编辑控件。 '''{{{filling}}}模块''' {{{filling}}}模块包含所有使用户能够浏览对象的名字空间和显示关于那些对象的运行时信息的{{{GUI}}}控件。 定义在{{{filling}}}模块中的四个类的说明见下表4.7 表4.7 {{{FillingTree}}}:基于{{{wx.TreeCtrl}}},{{{FillingTree}}}提供对象名字空间的分级树。 {{{FillingText}}}:{{{editwindow.EditWindow}}}的子类,用以显示当前在{{{FillingTree}}}所选择的对象的细节。 {{{Filling}}}:一个{{{wx.SplitterWindow}}},它的左边包括一个{{{FillingTree}}},右边包括一个{{{FillingText}}}。 {{{FillingFrame}}}:一个包含{{{Filling}}}分隔窗口的框架。双击{{{filling}}}树中的一项将打开一个新的{{{FillingFrame}}},其中被选择的项作为树的根。 使用这些类,使你能够容量地创建{{{Python}}}名字空间的分级树。如果你设置你的数据为{{{Python}}}对象,这能够用作一个快速的数据浏览器。 '''{{{interpreter}}}模块''' {{{interpreter}}}模块定义了一个{{{Interpreter}}}类,基于{{{Python}}}标准库中的{{{code}}}模块的{{{Interactive}}}- {{{Interpreter}}}类。除了负责发送源代码给{{{Python}}}外,{{{Interpreter}}}类还负责提供自动完成列表,调用提示信息和甚至触发自动完成特性的关键码(通常是".")。 由于这清楚的责任划分,你可以创建你自己的{{{Interpreter}}}的子类并传递其一个实例到{{{PyCrust}}} {{{shell}}},从而代替默认的{{{interpreter}}}。这已经应用到了一些程序中以支持自定义评议种类,而仍然利用{{{PyCrust}}}环境。 '''{{{introspect}}}模块''' {{{introspect}}}模块被{{{Interpreter}}}和{{{FillingTree}}}类使用。它为调用提示和命令自动完成提供了多种内省类型支持功能。下面显示了{{{wx.py.introspect}}}的用法,它得到一个列表对象的所有属性的名字,排除了那些以双下划线开始的属性: {{{#!python import wx L = [1, 2, 3] wx.py.introspect.getAttributeNames(L, includeDouble=False) ['append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] }}} {{{getAttributeNames()}}}函数被{{{FillingTree}}}类使用以生成它的名字空间分级。理解内省模块的最好方法是关注单元测试。查看你的{{{Python}}}安装目录的{{{Lib}}}/{{{site}}}-{{{packages}}}/{{{wx}}}/{{{py}}}/{{{tests}}}中的{{{test_introspect.py}}}文件。 '''{{{shell}}}模块''' {{{shell}}}模块包含出现在{{{PyCrust}}}, {{{PyShell}}}, 和{{{PyAlaMode}}}定义{{{Python}}} {{{shell}}}界面的{{{GUI}}}元素。下表4.8提供了每个元素的说明。这最常用的类是{{{ShellFrame}}},它是{{{frame.Frame}}}的子类。它包含一个{{{Shell}}}类的实例,{{{Shell}}}类处理提供交互{{{Python}}}环境的大部分工作。 表4.8 定义在{{{shell}}}模块中的类 {{{Shell}}}:{{{Python}}} {{{shell}}}基于{{{wx.stc.StyleTextCtrl}}}。{{{Shell}}}子类化{{{editwindow.EditWindow}}},然后使底层的文本控件的行为像一具{{{Python}}} {{{shell}}},而非一个源码文件编辑器。 {{{ShellFacade}}}:简化的与所有{{{shell}}}相关的功能的接口。它是半透明的,它仍然是可访问的,尽管只有一些是对{{{shell}}}用户可见的。 {{{ShellFrame}}}:一个包含一个{{{Shell}}}窗口的框架。 {{{ShellFacade}}}类在{{{PyCrust}}}的开发期间被创建,它作为在{{{shell}}}中访问{{{shell}}}对象自身时去简化事情的一个方法。当你启动{{{PyCrust}}}或{{{PyShell}}}时,{{{Shell}}}类的实例在{{{Python}}} {{{shell}}}中的有效的。例如,你可以在{{{shell}}}提示符下调用{{{shell}}}的{{{about()}}}方法,如下所示: {{{ shell.about() Author: "Patrick K. O'Brien pobrien@orbtech.com " Py Version: 0.9.4 Py Shell Revision: 1.7 Py Interpreter Revision: 1.5 Python Version: 2.3.3 wxPython Version: 2.4.1.0p7 Platform: linux2 }}} 由于{{{Shell}}}继承自{{{StyledTextCtrl}}},所以它包含超过600个属性。大部分属性对{{{shell}}}提示符是没用的,因此,{{{ShellFacade}}}被创建来限制当你进入{{{shell}}}时出现在自动完成列表中的属性的数量。目前,{{{shell}}}对象只显示最有用的{{{shell}}}属性的25个。 == 如何在wxPython中使用Py包中的模块? == 如果你不想在你的应用程序使用一个完整的{{{PyCrust}}}框架,那么该怎么做呢?如果你在一个框架中仅仅只想要{{{shell}}}界面,而在另一个框架中要一个名字空间查看器,该怎么办呢?如果你想把它们永久添加到你的程序中以该怎么办呢?这些方案不仅是可能的,而且也是十分简单的。我们将用一个例子来说明这该怎么做来结束本章。 我们将再看一下在第2章中所创建的程序,它带有一个菜单栏,工具栏和状态栏。我们将添加一个菜单,它的一个菜单项用以显示一个{{{shell}}}框架,另一个用来显示一个{{{filling}}}框架。最后我们将把{{{filling}}}树的根设置给我们的主程序的框架对象。结果显示在图4.11中。 '''图4.11''' {{attachment:w4.11.gif}} 下例4.7显示了修改过的源码。正如你的看到的,我们只增加了几行就实现了所要求的功能。 例4.7 {{{#!python #!/usr/bin/env python import wx #1 导入这些框架类 from wx.py.shell import ShellFrame from wx.py.filling import FillingFrame 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, -1) panel.SetBackgroundColour('White') statusBar = self.CreateStatusBar() toolbar = self.CreateToolBar() toolbar.AddSimpleTool(wx.NewId(), images.getNewBitmap(),"New", "Long help for 'New'") toolbar.Realize() menuBar = wx.MenuBar() menu1 = wx.Menu() menuBar.Append(menu1, " ") menu2 = wx.Menu() 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, " ")#2 创建Debug菜单及其菜单项 menu3 = wx.Menu() shell = menu3.Append(-1, " shell","Open wxPython shell frame") filling = menu3.Append(-1, " viewer","Open namespace viewer frame") menuBar.Append(menu3, " ")#3 设置菜单的事件处理器 self.Bind(wx.EVT_MENU, self.OnShell, shell) self.Bind(wx.EVT_MENU, self.OnFilling, filling) self.SetMenuBar(menuBar) def OnCloseMe(self, event): self.Close(True) def OnCloseWindow(self, event): self.Destroy() #4 OnShell菜单项和OnFilling菜单项处理器 def OnShell(self, event): frame = ShellFrame(parent=self) frame.Show() def OnFilling(self, event): frame = FillingFrame(parent=self) frame.Show() if __name__ == '__main__': app = wx.PySimpleApp() app.frame = ToolbarFrame(parent=None, id=-1) app.frame.Show() app.MainLoop() }}} '''说明:''' #1 这里我们导入了{{{ShellFrame}}}和{{{FillingFrame}}}类 #2 我们添加了第三个菜单{{{Debug}}}到框架的菜单栏 #3 绑定一个函数给{{{wx.EVT_MENU()}}},使我们能够将一个处理器与菜单项关联,以便当这个菜单项被选择时调用所关联的处理器。 #4 当用户从{{{Debug}}}菜单中选择{{{Python}}} {{{shell}}}时,{{{shell}}}框架被创建,它的双亲是工具栏框架。当工具栏框架被关闭时,任何打开的{{{shell}}}或{{{filling}}}框架也被关闭。 == 本章小结 == 1、像{{{wxPython}}}这样的工具包本身是大而复杂的。{{{GUI}}}控件之间的交互并不总是直观的,整个的处理决定于事件并响应于事件,而非一个线性的执行序列。使用如{{{PyCrust}}} {{{shell}}}能够很大程度上提高你对事件驱动环境的理解。 2、{{{PyCrust}}}仅仅是另一个{{{Python}}} {{{shell}}},它类似于{{{IDLE}}}, {{{Boa}}} {{{Constructor}}}, {{{PythonWin}}}和其它开发工具所包括的{{{shell}}}。然而,{{{PyCrust}}}是用{{{wxPython}}}创建的,当你使用{{{wxPython}}}开发程序时,它是很有用的。尤其是,你不会有任何事件循环冲突方面的问题,并且你可以在{{{PyCrust}}}的{{{shell}}}和名字空间查看器中处理你的程序运行时的所有方面。 3、由于{{{PyCrust}}}是{{{wxPython}}}发行版的一部分,所以它随同{{{wxPython}}}一同被安装,包括了所有的源码。这使得{{{PyCrust}}}容易使用,并且减少了摸清如何在你自己的程序中提供内省功能的学习曲线。 4、另外,{{{Py}}}包的模块化的设计,使你很容易去挑选最有益于你程序的模块,如源代码编辑、名字空间查看、或{{{shell}}} 5、{{{PyCrust}}}减少了{{{wxPython}}}学习的曲线,并帮助你掌握你的程序运行时的细微之处。 下一章,我们将应用我们所学到的关于{{{wxPython}}}方面的知识,并且提供一些关于如何组织你的{{{GUI}}}程序的实用的建议。