把窗口部件放入框架中

在你的wxPython中,所有的用户交互行为都发生在一个窗口部件容器中,它通常被称作窗口,在wxPython 中被称为框架。在这一章中,我们将讨论wxPython中的几个不同样式的框架。这个主要的wx.Frame有几个不同的框架样式,这些样式可以改变wx.Frame的外观。另外,wxPython提供了小型框架和实现多文档界面的框架。框架可以使用分隔条来划分为不同的部分,并且可以通过滚动条的使用来包含比框架本身大的面板(panel)。

框架的寿命

我们将通过讨论框架最基本的元素:创建和除去它们,来作为我们的开始。创建框架包括了解可以应用的所有样式元素;框架的去除可能比你原本想像的要复杂。

如何创建一个框架?

在本书中我们已经见过了许多的框架创建的例子,但是我们仍将再回顾一下框架创建的初步原则。

创建一个简单的框架

框架是类wx.Frame的实例。例8.1显示了一个非常简单的框架创建的例子。

例8.1 创建基本的wx.Frame

   1 import wx
   2 
   3 if __name__ == '__main__':
   4     app = wx.PySimpleApp()
   5     frame = wx.Frame(None, -1, "A Frame", style=wx.DEFAULT_FRAME_STYLE,
   6         size=(200, 100))
   7     frame.Show()
   8     app.MainLoop()

上面的代码创建一个带有标题的框架,其大小是(200,100)。表8.1中的默认样式提供了标准框架的装饰如关闭框、最小化和最大化框。结果如图8.1所示。

图8.1

w8.1.gif

wx.Frame的构造函数类似于我们在第7章见到的其它窗口部件的构造函数:

   1 wx.Frame(parent, id=-1, title="", pos=wx.DefaultPosition,
   2         size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
   3         name="frame")

这里有超过十余种之多的专用于wx.Frame的样式标记,我们将在下一部分涵盖它们。默认样式为你提供了最小化和最大化框、系统下拉菜单、可调整尺寸的粗边框和一个标题。

这里没有与一个wx.Frame挂钩的事件类型。但是,由于一个wx.Frame是你的屏幕上用户最可能去关闭的元素,所以你通常想去为关闭事件定义一个处理器,以便子窗口和数据被妥善的处理。

创建框架的子类

你将很少直接创建wx.Frame的实例。正如我们在本书中所见过的其它例子一样,一个典型的wxPython应用程序创建wx.Frame的子类并创建那些子类的实例。这是因为wx.Frame的独特的情形——虽然它自身定义了很少的行为,但是带有独自的初始化程序的子类是放置有关你的框架的布局和行为的最合理的地方。不创建子类而构造你应用程序的特定的布局是有可能,但除了最简单的应用程序以外,那是不容易的。例8.2展示了wx.Frame子类的例子。

例8.2 一个简单的框架子类

   1 import wx
   2 
   3 class SubclassFrame(wx.Frame):
   4     def __init__(self):
   5         wx.Frame.__init__(self, None, -1, 'Frame Subclass', 
   6                 size=(300, 100))
   7         panel = wx.Panel(self, -1)
   8         button = wx.Button(panel, -1, "Close Me", pos=(15, 15))
   9         self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button)
  10         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  11 
  12     def OnCloseMe(self, event):
  13         self.Close(True)
  14 
  15     def OnCloseWindow(self, event):
  16         self.Destroy()
  17 
  18 if __name__ == '__main__':
  19     app = wx.PySimpleApp()
  20     SubclassFrame().Show()
  21     app.MainLoop() 

运行结果如图8.2所示

图8.2

w8.2.gif

我们在许多其它的例子中已经见过了这种基本的结构,因此让我们来讨论上面代码中特定于框架的部分。wx.Frame.init方法与wx.Frame构造函数有相同的信息。子类自身的构造器除了self没有其它参数,它允许你作为程序员去定义参数,所定义的参数将传递给其父类,并且使你可以不用重复指定与父类相同的参数。

同样值得注意的是,框架的子窗口部件被放置在一个面板(panel)中。面板(panel)是类wx.Panel的实例,它是其它有较少功能的窗口部件的容器。你基本上应该使用一个wx.Panel作为你的框架的顶级子窗口部件。有一件事情就是,多层次的构造可以使得更多的代码能够重用,如相同的面板和布局可以被用于多个框架中。在框架中使用wx.Panel给了你一些对话框的功能。这些功能以成对的方式表现。其一是,在MS Windows操作系统下,wx.Panel实例的默认背景色以白色代替了灰色。其二,面板(panel)可以有一个默认的项目,该项目在当回车键被按下时自动激活,并且面板(panel)以与对话框大致相同的办法响应tab键盘事件,以改变或选择默认项目。

有些什么不同的框架样式?

wx.Frame有许多的可能的样式标记。通常,默认样式就是你想要的,但也有一些有用的变种。我们将讨论的第一组样式控制框架的形状和尺寸。尽管不是强制性的,但是这些标记应该被认为是互斥的——一个给定的框架应该只使用它们中的一个。表8.1说明了形状和尺寸标记。

表8.1 框架的形状和尺寸标记

wx.FRAME_NO_TASKBAR

一个完全标准的框架,除了一件事:在Windows系统和别的支持这个特性的系统下,它不显示在任务栏中。当最小化时,该框架图标化到桌面而非任务栏。

wx.FRAME_SHAPED

非矩形的框架。框架的确切形状使用SetShape()方法来设置。窗口的形状将在本章后面部分讨论。

wx.FRAME_TOOL_WINDOW

该框架的标题栏比标准的小些,通常用于包含多种工具按钮的辅助框架。在Windows操作系统下,工具窗口将不显示在任务栏中。

wx.ICONIZE

窗口初始时将被最小化显示。这个样式仅在Windows系统中起作用。

wx.MAXIMIZE

窗口初始时将被最大化显示(全屏)。这个样式仅在Windows系统中起作用。

wx.MINIMIZE

同wx.ICONIZE。

上面这组样式中,屏幕画面最需要的样式是wx.FRAME_TOOL_WINDOW。图8.3显示了一个小的结合使用了wx.FRAME_TOOL_WINDOW、wx.CAPTION和wx.SYSTEM_MENU样式的例子。

图8.3

w8.3.gif

这里有两个互斥的样式,它们控制一个框架是否位于别的框架的上面,无论别的框架是否获得了焦点。这对于那些小的不是始终可见的对话框是有用的。表8.2说明了这两个样式。最后,这还有一些用于放置在你的窗口上的装饰。如果你没有使用默认样式,那么这些装饰将不被自动放置到你的窗口上,你必须添加它们,否则容易导致窗口不能关闭或移动。表8.3给出了这些装饰的列表。

表8.2 针对窗口漂浮行为的样式

wx.FRAME_FLOAT_ON_PARENT

框架将漂浮在其父窗口(仅其父窗口)的上面。(很明显,要使用这个样式,框架需要有一个父窗口)。其它的框架可以遮盖这个框架。

wx.STAY_ON_TOP

该框架将始终在系统中其它框架的上面。(如果你有多个框架使用了这个样式,那么它们将相互重叠,但对于系统中其它的框架,它们仍在上面。)

默认的样式wx.DEFAULT_FRAME_STYLE等同于wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.CLOSE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU |wx.CAPTION。这个样式创建了一个典型的窗口,你可以调整大小,最小化,最大化,或关闭。一个很好的主意就是当你想要使用除默认样式以外的样式时,将默认样式与其它的样式组合在一起,以确保你有正确的一套装饰。例如,要创建一个工具框架,你可以使用style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_TOOL_WINDOW。记住,你可以使用^操作符来去掉不要的样式。

表8.3 用于装饰窗口的样式

wx.CAPTION

给窗口一个标题栏。如果你要放置最大化框、最小化框、系统菜单和上下文帮助,那么你必须包括该样式。

wx.FRAME_EX_CONTEXTHELP

这是用于Windows操作系统的,它在标题栏的右角放置问号帮助图标。这个样式是与wx.MAXIMIZE_BOX和WX.MINIMIZE_BOX样式互斥的。它是一个扩展的样式,并且必须使用两步来创建,稍后说明。

wx.FRAME_EX_METAL

在Mac OS X上,使用这个样式的框架有一个金属质感的外观。这是一个附加样式,必须使用SetExtraStyle方法来设置。

wx.MAXIMIZE_BOX

在标题栏的标准位置放置一个最大化框。

wx.MINIMIZE_BOX

在标题栏的标准位置放置一个最小化框。

wx.CLOSE_BOX

在标题栏的标准位置放置一个关闭框。

wx.RESIZE_BORDER

给框架一个标准的可以手动调整尺寸的边框。

wx.SIMPLE_BORDER

给框架一个最简单的边框,不能调整尺寸,没有其它装饰。该样式与所有其它装饰样式是互斥的

wx.SYSTEM_MENU

在标题栏上放置一个系统菜单。这个系统菜单的内容与你所使用的装饰样式有关。例如,如果你使用wx.MINIMIZE_BOX,那么系统菜单项就有“最小化”选项。

如何创建一个有额外样式信息的框架?

wx.FRAME_EX_CONTEXTHELP是一个扩展样式,意思是样式标记的值太大以致于不能使用通常的构造函数来设置(因为底层C++变量类型的特殊限制)。通常你可以在窗口部件被创建后,使用SetExtraStyle方法来设置额外的样式,但是某些样式,比如wx.FRAME_EX_CONTEXTHELP,必须在本地UI(用户界面)对象被创建之前被设置。在wxPython中,这需要使用稍微笨拙的方法来完成,即分两步构建。之后标题栏中带有我们熟悉的问号图标的框架就被创建了。如图8.4所示。

图8.4

w8.4.gif

标记值必须使用SetExtraStyle()方法来设置。有时,额外样式信息必须在框架被实例化前被设置,这就导致了一个问题:你如何对于一个不存在的实例调用一个方法?在接下来的部分,我们将展示实现这种操作的两个机制。

添加额外样式信息

在wxPython中,额外样式信息在创建之前通过使用专门的类wx.PreFrame来被添加,它是框架的一种局部实例。你可以在预框架(preframe)上设置额外样式位,然后使用这个预框架(preframe)来创建实际的框架。例8.3显示了在一个子类的构造器中如何完成这两步(two-step)的构建。注意,在wxPython中它实际上是三步(在C++ wxWidgets工具包中,它是两步(two-step),我们只是沿用这个叫法而已)。

例8.3

   1 import wx
   2 
   3 class HelpFrame(wx.Frame):
   4 
   5     def __init__(self):
   6         pre = wx.PreFrame() #1 预构建对象
   7         pre.SetExtraStyle(wx.FRAME_EX_CONTEXTHELP)
   8         pre.Create(None, -1, "Help Context", size=(300, 100),
   9                 style=wx.DEFAULT_FRAME_STYLE ^
  10                 (wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX)) #2 创建框架
  11         self.PostCreate(pre) #3 底层C++指针的传递
  12 
  13 if __name__ == '__main__':
  14     app = wx.PySimpleApp()
  15     HelpFrame().Show()
  16     app.MainLoop()

#1 创建wx.PreFrame()的一个实例(关于对话框,这有一个类似的wx.PreDialog()——其它的wxWidgets窗口部件有它们自己的预类)。在这个调用之后,你可以做你需要的其它初始化工作。

#2 调用Create()方法创建框架。

#3 这是特定于wxPython的,并且不由C++完成。PostCreate方法做一些内部的内务处理,它实例化一个你在第一步中创建的封装了C++的对象。

添加额外样式信息的通用方法

先前的算法有点笨拙,但是它可以被重构得容易一点,以便于管理维护。第一步是创建一个公用函数,它可以管理任何分两步的创建。例8.4提供了一个例子,它使用Python的内省性能来调用以变量形式被传递的函数。这个例子用于在Python的一个新的框架实例化期间的init方法中被调用。

例8.4 一个公用的两步式创建函数

   1 def twoStepCreate(instance, preClass, preInitFunc, *args,**kwargs):
   2         pre = preClass()
   3         preInitFunc(pre)
   4         pre.Create(*args, **kwargs)
   5         instance.PostCreate(pre)

在例8.4中,函数要求三个必须的参数。instance参数是实际被创建的实例。preClass参数是临时的预类的类对象——对框架预类是wx.PreFrame。preInitFunc是一个函数对象,它通常作为回调函数用于该实例的初始化。这三个参数之后,我们可以再增加任意数量的其它可选参数。

这个函数的第一行,pre = preClass(),内省地实例化这个预创建对象,使用作为参数传递过来的类对象。下面一行根据参数preInitFunc内省地调用回调函数,它通常设置扩展样式标记。然后pre.Create()方法被调用,它使用了可选的参数。最后,PostCreate方法被调用来将内在的值从pre移给实例。至此,instance参数已经完全被创建了。假设twoStepCreate已被导入,那么上面的公用函数可以如例8.5被使用。

例8.5 另一个两步式的创建,使用了公用函数

   1 import wx
   2 
   3 class HelpFrame(wx.Frame):
   4 
   5 def __init__(self, parent, ID, title,pos=wx.DefaultPosition, size=(100,100),style=wx.DEFAULT_DIALOG_STYLE):
   6         twoStepCreate(self, wx.PreFrame, self.preInit, parent,
   7         id, title, pos, size, style)
   8 
   9 def preInit(self, pre):
  10         pre.SetExtraStyle(wx.FRAME_EX_CONTEXTHELP)

类wx.PreFrame和函数self.preInit被传递给公用函数,并且preInit方法被定义为回调函数。

当关闭一个框架时都发生了什么?

当你关闭一个框架时,它最终消失了。除非这个框架被明确地告诉不关闭。换句话说,那关闭不是直接了当的。在wxPython的窗口部件关闭体系之后的用意是,给正在关闭的窗口部件充足的机会来关闭或释放它所占用任何非wxPython资源。如果你占用了某种昂贵的外部资源,如一个大的数据结构或一个数据库连接,那么该意图是特别受欢迎的。

诚然,在C++ wxWidgets世界里,由于C++不为你管理内在分配的清理工作,管理资源是更严肃的问题。在wxPython中,对于多步的关闭过程的显式需求就很少,但它对于在过程中使用额外的钩子仍然是有用的。(随便说一下,我们在这一节中从单词“框架”切换到单词“窗口部件”是故意的——因为在本节中的所有内容都适用于所有顶级窗口部件,如框架或对话框)。

何时用户触发关闭过程

关闭过程最常由用户触发,如敲击一个关闭框或选择系统菜单中的关闭项或当应用程序响应其它某个事件而调用框架的Close方法。当上述情况发生时,wxPython架构引发一个EVT_CLOSE事件。像wxPython 架构中的其它别的事件一样,你可以在绑定一个事件处理器以便一个EVT_CLOSE事件发生时调用。

如果你不声明你自己的事件处理器,那么默认的行为将被调用。默认的行为对于框架和对话框是不同的。

1、默认情况下,框架处理器调用Destroy()方法并删除该框架和它的所有的组件。

2、默认情况下,对话框的关闭处理器不销毁该对话框——它仅仅模拟取消按钮的按下,并隐藏对话框。该对话框对象仍继续存在在内存中。因此,如果需要的话,应用程序可以从它的数据输入部件获取值。当应用程序完成了对对话框的使用后,应该调用对话框的Destroy()方法。

如果你编写你自己的关闭处理器,那么你可以使用它来关闭或删除任何外部的资源,但是,如果你选择去删除框架的话,显式地调用Destroy()方法是你的责任。尽管Destroy()经常被Close()调用,但是只调用Close()方法不能保证框架的销毁。在一定的情形下,决定不销毁框架是完全可以的,如当用户取消了关闭。然而,你仍然需要一个方法来销毁该框架。如果你选择不去销毁窗口,那么调用关闭事件的wx.CloseEvent.Veto()方法来通知相关部分:框架拒绝关闭,是一个好的习惯。

如果你选择在你的程序的别处而非关闭处理器中关闭你的框架,例如从一个不同的用户事件像一个菜单项,那么我们建议使用的机制是调用框架的Close()方法。这将启动一个和系统关闭行为相同的过程。如果你要确保框架一定被删除,那么你可以直接调用Destroy()方法;然而,如果你这样做了,可能会导致框架所管理的资源或数据没有被释放或保存。

什么时候系统触发关闭过程

如果关闭事件是由系统自己触发的,对于系统关闭或类似情况,你也有一种办法管理该事件。wx.App 类接受一个EVT_QUERY_END_SESSION事件,如果需要的话,该事件使你能够否决应用程序的关闭,如果所有运行的应用已经批准了系统或GUI环境的关闭的话,那么随后会有一个EVT_END_SESSION事件。你选择去否决关闭的行为是与依赖于具体系统的。

最后,值得注意的是,调用一个窗口部件的Destroy()方法不意味该部件被立即销毁。销毁实际上是当事件循环在未来空闲时(任何未被处理的事件被处理之后)才被处理的。这就防止了处理已不存在的窗口部件的事件。

在接下来的两节,我们的讨论将从一个框架的生命周期切换到在框架生命周期里,你能够用框架来做些什么。

使用框架

框架包含了许多方法和属性。其中最重要的是那些查找框架中任意窗口部件的方法,和滚动框架中内容的方法。在这一节,我们将讨论如何实现这些。

wx.Frame有那些方法和属性?

这部分中的表包含了wx.Frame和它的父类wx.Window的最基本的属性。这些属性和方法的许多在本书中的其它地方有更详细的说明。表8.4包含了wx.Frame的一些公共的可读、可修改的属性。

表8.4 wx.Frame的公共属性

GetBackgroundColor(),SetBackgroundColor(wx.Color)

背景色是框架中没有被其子窗口部件覆盖住的那些部分的颜色。你可以传递一个wx.Color或颜色名给设置方法。任何传递给需要颜色的wxPython方法的字符串,都被解释为对函数wx.NamedColour()的调用。

GetId(),SetId(int)

返回或设置窗口部件的标识符。

GetMenuBar(),SetMenuBar(wx.MenuBar)

得到或设置框架当前使用的的菜单栏对象,如果没有菜单栏,则返回None。

GetPosition(),GetPositionTuple(),SetPosition(wx.Point)

以一个wx.Point或Python元组的形式返回窗口左上角的x,y的位置。对于顶级窗口,该位置是相对于显示区域的坐标,对于子窗口,该位置是相对于父窗口的坐标。

GetSize() GetSizeTuple() SetSize(wx.Size):C++版的get*或set*方法被覆盖。默认的get*或set*使用一个wx.Size对象。GetSizeTuple()方法以一个Python元组的形式返回尺寸。也可以参看访问该信息的另外的方法SetDimensions()。

GetTitle() SetTitle(String):得到或设置框架标题栏的字符串。

表8.5显示了一些wx.Frame的非属性类的更有用的方法。其中要牢记的是Refresh(),你可以用它来手动触发框架的重绘。

表8.5 wx.Frame的方法

Center(direction=wx.BOTH):框架居中(注意,非美语的拼写Centre,也被定义了的)。参数的默认值是wx.BoTH,在此情况下,框是在两个方向都居中的。参数的值若是wx.HORIZONTAL或wx.VERTICAL,表示在水平或垂直方向居中。

Enable(enable=true):如果参数为true,则框架能够接受用户的输入。如果参数为False,则用户不能在框架中输入。相对应的方法是Disable()。

GetBestSize():对于wx.Frame,它返回框架能容纳所有子窗口的最小尺寸。

Iconize(iconize):如果参数为true,最小化该框架为一个图标(当然,具体的行为与系统有关)。如果参数为False,图标化的框架恢复到正常状态。

IsEnabled():如果框架当前有效,则返回True。

IsFullScreen():如果框架是以全屏模式显示的,则返回True,否则False。细节参看ShowFullScreen

IsIconized():如果框架当前最小化为图标了,则返回True,否则False。

IsMaximized():如果框架当前是最大化状态,则返回True,否则False。

IsShown():如果框架当前可见,则返回True。

IsTopLevel():对于顶级窗口部件如框架或对话框,总是返回True,对于其它类型的窗口部件返回False。

Maximize(maximize):如果参数为True,最大化框架以填充屏幕(具体的行为与系统有关)。这与敲击框架的最大化按钮所做的相同,这通常放大框架以填充桌面,但是任务栏和其它系统组件仍然可见。

Refresh(eraseBackground=True, rect=None):触发该框架的重绘事件。如果rect是none,那么整个框架被重画。如果指定了一个矩形区域,那么仅那个矩形区域被重画。如果eraseBackground为True,那么这个窗口的北影也将被重画,如果为False,那么背景将不被重画。

SetDimensions(x, y, width, height, sizeFlags=wx.SIZE_AUTO):使你能够在一个方法调用中设置窗口的尺寸和位置。位置由参数x和y决定,尺寸由参数width和height决定。前四个参数中,如果有的为-1,那么这个-1将根据参数sizeFlags的值作相应的解释。表8.6包含了参数sizeFlags的可能取值。

Show(show=True):如果参数值为True,导致框架被显示。如果参数值为False,导致框架被隐藏。Show(False)等同于Hide()。

ShowFullScreen(show, style=wx.FULLSCREEN_ALL):如果布尔参数是True,那么框架以全屏的模式被显示——意味着框架被放大到填充整个显示区域,包括桌面上的任务栏和其它系统组件。如果参数是False,那么框架恢复到正常尺寸。style参数是一个位掩码。默认值wx.FULLSCREEN_ALL指示wxPython当全屏模式时隐藏所有窗口的所有样式元素。后面的这些值可以通过使用按位运算符来组合,以取消全屏模式框架的部分装饰:wx.FULLSCREEN_NOBORDER, wx.FULLSCREEN_NOCAPTION, wx.FULLSCREEN_NOMENUBAR, wx.FULLSCREEN_NOSTATUSBAR, wx.FULLSCREEN_NOTOOLBAR。

表8.5中说明的SetDimensions()方法在用户将一个尺寸指定为-1时,使用尺寸标记的一个位掩码来决定默认行为。表8.6说明了这些标记。

这些方法没有涉及框架所包含的孩子的位置问题。这个问题要求框架的孩子自已去说明它。

表8.6 关于SetDimensions方法的尺寸标记

wx.ALLOW_MINUS_ONE:一个有效的位置或尺寸。

wx.SIZE_AUTO:转换为一个wxPython默认值。

wx.SIZE_AUTO_HEIGHT:一个有效的高度,或一个wxPython默认高度。

wx.SIZE_AUTO_WIDTH:一个有效的宽度,或一个wxPython默认宽度。

wx.SIZE_USE_EXISTING:使用现有的尺寸。

如何查找框架的子窗口部件?

有时候,你将需要查找框架或面板(panel)上的一个特定的窗口部件,并且你没有它的相关引用。如第6章所示的这种情况的一个公用的应用程序,它查找与所选菜单相关的实际的菜单项对象(因为事件不包含对它的一个引用)。另一种情况就是,当你想基于一个项的事件去改变系统中其它任一窗口部件的状态时。例如,你可能有一个按钮和一个菜单项,它们互相改变彼此的开关状态。当按钮被敲击时,你需要去得到菜单项以触发它。例8.6显示了一个摘自第7章的一个小的例子。在这个代码中,FindItemById()方法用来去获得与事件对象所提供的ID相关的菜单项。该项的标签被用来驱动所要求的颜色的改变。

例8.6 通过ID查找项目的函数

   1 def OnColor(self, event):
   2         menubar = self.GetMenuBar()
   3         itemId = event.GetId()
   4         item = menubar.FindItemById(itemId)
   5         color = item.GetLabel()
   6         self.sketch.SetColor(color)

在wxPython中,有三种查找子窗口部件的方法,它们的行为都很相似。这些方法对任何作为容器的窗口部件都是适用的,不单单是框架,还有对话框和面板(panel)。你可以通过内部的wxPython ID查寻一个窗口部件,或通过传递给构造函数的名字(在name参数中),或通过文本标签来查寻。文本标签被定义为相应窗口部件的标题,如按钮和框架。

这三种方法是:

这三种情况中,parent参数可以被用来限制为对一个特殊子层次的搜索(也就是,它等同于父类的Find方法)。还有,FindWindowByName()首先按name参数查找,如果没有发现匹配的,它就调用FindWindowByLabel()去查找一个匹配。

如何创建一个带有滚动条的框架?

在wxPython中,滚动条不是框架本身的一个元素,而是被类wx.ScrolledWindow控制。你可以在任何你要使用wx.Panel的地方使用wx.ScrolledWindow,并且滚动条移动所有在滚动窗口中的项目。图8.5和图8.6显示了滚动条,包括它的初始状态和滚动后的状态。从图8.5到图8.6,左上的按钮移出了视野,右下的按钮移进了视野。

在这一节,我们将讨论如何去创建一个带有滚动条的窗口以及如何在你的程序中处理滚动行为。

图8.5

w8.5.gif

图8.6

w8.5.gif

如何创建滚动条

例8.7显示了用于创建滚动窗口的代码。

例8.7 创建一个简单的滚动窗口

   1 import wx
   2 
   3 class ScrollbarFrame(wx.Frame):
   4     def __init__(self):
   5         wx.Frame.__init__(self, None, -1, 'Scrollbar Example', 
   6                 size=(300, 200))
   7         self.scroll = wx.ScrolledWindow(self, -1)
   8         self.scroll.SetScrollbars(1, 1, 600, 400)
   9         self.button = wx.Button(self.scroll, -1, "Scroll Me", pos=(50, 20))
  10         self.Bind(wx.EVT_BUTTON,  self.OnClickTop, self.button)
  11         self.button2 = wx.Button(self.scroll, -1, "Scroll Back", pos=(500, 350))
  12         self.Bind(wx.EVT_BUTTON, self.OnClickBottom, self.button2)
  13 
  14     def OnClickTop(self, event):
  15         self.scroll.Scroll(600, 400)
  16         
  17     def OnClickBottom(self, event):
  18         self.scroll.Scroll(1, 1)
  19         
  20 if __name__ == '__main__':
  21     app = wx.PySimpleApp()
  22     frame = ScrollbarFrame()
  23     frame.Show()
  24     app.MainLoop()

wx.ScrolledWindow的构造函数几乎与wx.Panel的相同:

   1 wx.ScrolledWindow(parent, id=-1, pos=wx.DefaultPosition,
   2         size=wx.DefaultSize, style=wx.HSCROLL | wx.VSCROLL,
   3         name="scrolledWindow")

所有的这些属性的行为都如你所愿,尽管size属性是它的父亲中的面板的物理尺寸,而非滚动窗口的逻辑尺寸。

指定滚动区域的尺寸

有几个自动指定滚动区域尺寸的方法。手工指定最多的方法如例8.1所示,使用了方法SetScrollBars

   1 SetScrollbars(pixelsPerUnitX, pixelsPerUnitY, noUnitsX, noUnitsY,
   2         xPos=0, yPos=0, noRefresh=False)

其中关键的概念是滚动单位,它是滚动条的一次移动所引起的窗口中的转移距离。前面的两个参数pixelsPerUnitX和PixelsPerUnitY使你能够在两个方向设置滚动单位的大小。接下来的两个参数noUnitsX和noUnitsY使你能够按滚动单位设置滚动区域的尺寸。换句话说,滚动区域的象素尺寸是(pixelsPerUnitX* noUnitsX, pixelsPerUnitY * noUnitsY)。例8.7通过将滚动单位设为1像素而避免了可能造成的混淆。参数xPos和yPos以滚动单位(非像素)为单位,它设置滚动条的初始位置,如果参数noRefresh为true,那么就阻止了在因SetScrollbars()的调用而引起的滚动后的窗口的自动刷新。

还有另外的三个方法,你可以用来设置滚动区域的尺寸,然后单独设置滚动率。你可能发现这些方法更容易使用,因为它们使你能够更直接地指定尺寸。你可以如下以像素为单位使用滚动窗口的SetVirtualSize()方法来直接设置尺寸。

   1 self.scroll.SetVirtualSize((600, 400))

使用方法FitInside(),你可以在滚动区域中设置窗口部件,以便滚动窗口绑定它们。这个方法设置滚动窗口的边界,以使滚动窗口刚好适合其中的所有子窗口:

   1 self.scroll.FitInside()

通常使用FitInside()的情况是,当在滚动窗口中正好有一个窗口部件(如文本域),并且该窗口部件的逻辑尺寸已被设置。如果我们在例8.7中使用了FitInside(),那么一个更小的滚动区域将被创建,因为该区域将正好匹配右下按钮的边缘,而没有多余的内边距。

最后,如果滚动窗口中有一个sizer设置,那么使用SetSizer()设置滚动区域为sizer所管理的窗口部件的尺寸。这是在一个复杂的布局中最常用的机制。关于sizer的更多细节参见第11章。

对于上述所有三种机制,滚动率需要去使用SetScrollRate()方法单独设置,如下所示:

   1 self.scroll.SetScrollRate(1, 1)

参数分别是x和y方向的滚动单位尺寸。大于0的尺寸都是有效的。

滚动条事件

在例8.7中的按钮事件处理器,使用Scroll()方法程序化地改变滚动条的位置。这个方法需要滚动窗口的x和y坐标,使用的是滚动单位。

在第7章中,我们答应了你可以捕获的来自滚动条的事件列表,因为它们也被用来去控制滑块。表8.7列出了所有被滚动窗口内在处理的滚动事件。通常,许多你不会用到,除非你建造自定义窗口部件。

表8.7 滚动条的事件

EVT_SCROLL

当任何滚动事件被触发时发生。

EVT_SCROLL_BOTTOM

当用户移动滚动条到它的范围的最末端时触发(底边或右边,依赖于方向)。

EVT_SCROLL_ENDSCROLL

在微软的Windows中,任何滚动会话的结束都将触发该事件,不管是因鼠标拖动或按键按下。

EVT_SCROLL_LINEDOWN

当用户向下滚动一行时触发。

EVT_SCROLL_LINEUP

当用户向上滚动一行时触发。

EVT_SCROLL_PAGEDOWN

当用户向下滚动一页时触发。

EVT_SCROLL_PAGEUP

当用户向上滚动一页时触发。

EVT_SCROLL_THUMBRELEASE

用户使用鼠标拖动滚动条滚动不超过一页的范围,并释放鼠标后,触发该事件。

EVT_SCROLL_THUMBTRACK

滚动条在一页内被拖动时不断的触发。

EVT_SCROLL_TOP

当用户移动滚动条到它的范围的最始端时触发,可能是顶端或左边,依赖于方向而定。

行和页的准确定义依赖于你所设定的滚动单位,一行是一个滚动单位,一页是滚动窗口中可见部分的全部滚动单位的数量。对于表中所列出的每个EVT_SCROLL*事件,都有一个相应的EVT_SCROLLWIN*事件(它们由wx.ScrolledWindow产生)来回应。

还有一个wxPython的特殊的滚动窗口子类:wx.lib.scrolledpanel.ScrolledPanel,它使得你能够在面板上自动地设置滚动,该面板使用一个sizer来管理子窗口部件的布局。wx.lib.scrolledpanel.ScrolledPanel增加的好处是,它让用户能够使用tab键来在子窗口部件间切换。面板自动滚动,使新获得焦点的窗口部件进入视野。要使用wx.lib.scrolledpanel.ScrolledPanel,就要像一个滚动窗口一样声明它,然后,在所有的子窗口被添加后,调用下面的方法:

   1 SetupScrolling(self, scroll_x=True, scroll_y=True, rate_x=20, 
   2         rate_y=20) 

rate_x和rate_y是窗口的滚动单位,该类自动根据sizer所计算的子窗口部件的尺寸设定虚拟尺寸(virtual size)。 记住,当确定滚动窗口中的窗口部件的位置的时候,该位置总是窗口部件的物理位置,它相对于显示器中的滚动窗口的实际原点,而非窗口部件相对于显示器虚拟尺寸(virtual size)的逻辑位置。这始终是成立的,即使窗口部件不再可见。例如,在敲击了图8.5中的Scroll Me按钮后,该按钮所报告的它的位置是(-277,-237)。如果这不的你所想要的,那么使用CalcScrolledPosition(x,y)和。CalcUnscrolledPosition(x, y)方法在显示器坐标和逻辑坐标之间切换。在这两种情况中,在按钮敲击并使滚动条移动到底部后,你传递指针的坐标,并且滚动窗口返回一个(x,y)元组,如下所示:

   1 CalcUnscrolledPostion(-277, -237) #

可选的框架类型

框架不限于其中带有窗口部件的普通的矩形,它可以呈现其它的形状。你也可以创建MDI(多文档界面)框架,它其中包含别的框架。或者你也可以去掉框架的标题栏,并且仍然可以使用户能拖动框架。

如何创建一个MDI框架?

还记得MDI吗?许多人都不记得了。MDI是微软90年代初的创新,它使得一个应用程序中的多个子窗口能被一个单一的父窗口控制,本质上为每个应用程序提供了一个独立的桌面。在大多数应用程序中,MDI要求应用程序中的所有窗口同时最小化,并保持相同的z轴次序(相对系统中的其它部分)。我们建议仅当用户期望同时看到所有的应用程序窗口的情况下使用MDI,例如一个游戏。图8.7显示了一个典型的MDI环境。

在wxPython中MDI是被支持的,在Windows操作系统下通过使用本地窗口部件来实现MDI,在其它的操作系统中通过模拟子窗口实现MDI。例8.8提供了一简单的MDI的例子。

图8.7

w8.7.gif

例8.8 如何创建一个MDI窗口

   1 import wx
   2 
   3 class MDIFrame(wx.MDIParentFrame):
   4     def __init__(self):
   5         wx.MDIParentFrame.__init__(self, None, -1, "MDI Parent", 
   6                 size=(600,400))
   7         menu = wx.Menu()
   8         menu.Append(5000, "  Window")
   9         menu.Append(5001, "E ")
  10         menubar = wx.MenuBar()
  11         menubar.Append(menu, " ")
  12         self.SetMenuBar(menubar)
  13         self.Bind(wx.EVT_MENU, self.OnNewWindow, id=5000)
  14         self.Bind(wx.EVT_MENU, self.OnExit, id=5001)
  15 
  16     def OnExit(self, evt):
  17         self.Close(True)
  18 
  19     def OnNewWindow(self, evt):
  20         win = wx.MDIChildFrame(self, -1, "Child Window")
  21         win.Show(True)
  22 
  23 if __name__ == '__main__':
  24     app = wx.PySimpleApp()
  25     frame = MDIFrame()
  26     frame.Show()
  27     app.MainLoop()

MDI的基本概念是十分简单的。父窗口是wx.MDIParentFrame的一个子类,子窗口如同任何其它的wxPython窗口部件一样被添加,除了它们是wx.MDIChildFrame的子类。wx.MDIParentFrame的构造函数与wx.Frame的基本相同,如下所示:

   1 wx.MDIParentFrame(parent, id, title, pos = wx.DefaultPosition, 
   2         size=wxDefaultSize, 
   3         style=wx.DEFAULT_FRAME_STYLE | wx.VSCROLL | wx.HSCROLL, 
   4         name="frame")

不同的一点是wx.MDIParentFrame在默认情况下有滚动条。wx.MDIChildFrame的构造函数是相同的,除了它没有滚动条。如例8.8所示,添加一个子框架是通过创建一个以父框架为父亲的框架来实现的。

你可以通过使用父框架的Cascade()或Tile()方法来同时改变所有子框架的位置和尺寸,它们模拟相同名字的菜单项。调用Cascade(),导致一个窗口显示在其它的上面,如图8.7的所示,而Tile()使每个窗口有相同的尺寸并移动它们以使它们不重叠。要以编程的方式在子窗口中移动焦点,要使用父亲的方法ActivateNext()和ActivatePrevious()

什么是小型框架,我们为何要用它?

小型框架是一个有两个例外的矩形框架:它有一个较小的标题区域,并且在微软的Windows下或GTK下,它不在任务栏中显示。图8.8显示了一个较小标题域的一个例子。

图8.8 一个小型框架

w8.8.gif

创建小型框架的代码基本上等同于创建一个矩形框架,唯一的不同是父类是wx.MiniFrame。例8.9显示了这个代码。

例8.9 创建一个小型框架

   1 import wx
   2 
   3 class MiniFrame(wx.MiniFrame):
   4     def __init__(self):
   5         wx.MiniFrame.__init__(self, None, -1, 'Mini Frame', 
   6                 size=(300, 100))
   7         panel = wx.Panel(self, -1, size=(300, 100))
   8         button = wx.Button(panel, -1, "Close Me", pos=(15, 15))
   9         self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button)
  10         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  11 
  12     def OnCloseMe(self, event):
  13         self.Close(True)
  14 
  15     def OnCloseWindow(self, event):
  16         self.Destroy()
  17 
  18 if __name__ == '__main__':
  19     app = wx.PySimpleApp()
  20     MiniFrame().Show()
  21     app.MainLoop() 

wx.MiniFrame的构造函数等同于wx.Frame的,然而,wx.MiniFrame支持额外的样式标记。如表8.8所示。

表8.8 wx.MiniFrame的样式标记

wx.THICK_FRAME

在Windows或Motif下,使用粗边框绘制框架。

wx.TINY_CAPTION_HORIZONTAL

代替wx.CAPTION而显示一个较小的水平标题。

wx.TINY_CAPTION_VERTICAL

代替wx.CAPTION而显示一个较小的垂直标题。

典型的,小型框架被用于工具框窗口中,在工具框窗口中始终是有效的,它们不影响任务栏。较小的标题使得它们更有效的利用空间,并且明显地区别于标准的框架。

如何创建一个非矩形的框架?

在大多数应用程序中,框架都是矩形,因为矩形有一个不错的规则的形状,并且绘制和维护相对简单。可是,有时候你需要打破直线的制约。在wxPython中,你可以给框架一个任一的形状。如果一个备用的形状被定义了,那么框架超出该形状的部分不将被绘制,并且不响应鼠标事件;对于用户而言,它们不是框架的一部分。图8.9显示了一个非矩形的窗口,显示的背景是文本编辑器中的代码。

事件被设置来以便双击时开关这个非标准形状,鼠标右键单击时关闭这个窗口。这个例子使用了来自wxPython demo的images模块作为vippi的图像的资源,wxPython的吉祥物。

图8.9

w8.9.gif

例8.10显示了在这个非矩形框架后面的代码。这个例子比我们见过的其它一些稍微精细点,以显示如何在缺少典型的窗口界面装饰的情况下管理像窗口关闭之类的事情。

例8.10 绘制符合形状的窗口

   1 import wx
   2 import images
   3 
   4 class ShapedFrame(wx.Frame):
   5     def __init__(self):
   6         wx.Frame.__init__(self, None, -1, "Shaped Window",
   7                 style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER |
   8                 wx.FRAME_NO_TASKBAR)
   9         self.hasShape = False
  10 
  11 #1 获取图像
  12         self.bmp = images.getVippiBitmap()
  13         self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight()))
  14 
  15 #2 绘制图像
  16         dc = wx.ClientDC(self)
  17         dc.DrawBitmap(self.bmp, 0,0, True)
  18 
  19         self.SetWindowShape()
  20         self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
  21         self.Bind(wx.EVT_RIGHT_UP, self.OnExit)
  22         self.Bind(wx.EVT_PAINT, self.OnPaint)
  23         self.Bind(wx.EVT_WINDOW_Create, self.SetWindowShape)#3 绑定窗口创建事件
  24 
  25     def SetWindowShape(self, evt=None):#4 设置形状
  26         r = wx.RegionFromBitmap(self.bmp)
  27         self.hasShape = self.SetShape(r)
  28 
  29     def OnDoubleClick(self, evt):
  30         if self.hasShape:
  31             self.SetShape(wx.Region())#5 重置形状
  32             self.hasShape = False
  33         else:
  34             self.SetWindowShape()
  35 
  36     def OnPaint(self, evt):
  37         dc = wx.PaintDC(self)
  38         dc.DrawBitmap(self.bmp, 0,0, True)
  39 
  40     def OnExit(self, evt):
  41         self.Close()
  42 
  43 if __name__ == '__main__':
  44     app = wx.PySimpleApp()
  45     ShapedFrame().Show()
  46     app.MainLoop()

#1 在从images模块得到图像后,我们将窗口内部的尺寸设置为位图的尺寸。你也可以根据一个标准的图像文件来创建这个wxPython位图,这将在第16章中作更详细的讨论。

#2 这里,我们在窗口中绘制这个图像。这决不是一个必然的选择。你可以像其它窗口一样在该形状窗口中放置窗口部件和文本(尽管它们必须在该形状的区域内)。

#3 这个事件在大多数平台上是多余的,它强制性地在窗口被创建后调用SetWindowShape()。但是,GTK的实现要求在该形状被设置以前,窗口的本地UI对象被创建和确定,因此当窗口创建发生时我们使用窗口创建事件去通知并在它的处理器中设置形状。

#4 我们使用全局方法wx.RegionFromBitmap去创建设置形状所需的wx.Region对象。这是创建不规则形状的最容易的方法。你也可以根据一个定义多边形的点的列表来创建一个wx.Region。图像的透明部分的用途是定义区域的边界。

#5 双击事件开关窗口的形状。要回到标准的矩形,要使用一个空的wx.Region作为参数来调用SetShape()。

除了没有标准的关闭框或标题栏等外,不规则形状框架的行为像一个普通的框架一样。任何框架都可以改变它的形状,因为SetShape()方法是wx.Frame类的一部分,它可以被任何子类继承。在wx.SplashScreen中,符合形状的框架是特别的有用。

如何拖动一个没有标题栏的框架?

前一个例子的明显结果是这个没有标题栏的框架不能被拖动的,这儿没有拖动窗口的标准方法。要解决这个问题,我们需要去添加事件处理器来在当拖动发生时移动该窗口。例8.11显示与前一例子相同形状的窗口,但增加了对于处理鼠标左键敲击和鼠标移动的一些事件。这个技术可以适用于任何其它的框架,甚至是框架内你想要移动的窗口(例如绘画程序中的元素)。

例8.11 使用户能够从框架来拖动框架的事件

   1 import wx
   2 import images
   3 
   4 class ShapedFrame(wx.Frame):
   5     def __init__(self):
   6         wx.Frame.__init__(self, None, -1, "Shaped Window",
   7                 style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER )
   8         self.hasShape = False
   9         self.delta = wx.Point(0,0)
  10         self.bmp = images.getVippiBitmap()
  11         self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight()))
  12         dc = wx.ClientDC(self)
  13         dc.DrawBitmap(self.bmp, 0,0, True)
  14         self.SetWindowShape()
  15         self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
  16 
  17 #1 新事件
  18         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
  19         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
  20         self.Bind(wx.EVT_MOTION, self.OnMouseMove)
  21 
  22         self.Bind(wx.EVT_RIGHT_UP, self.OnExit)
  23         self.Bind(wx.EVT_PAINT, self.OnPaint)
  24         self.Bind(wx.EVT_WINDOW_Create, self.SetWindowShape)
  25 
  26     def SetWindowShape(self, evt=None):
  27         r = wx.RegionFromBitmap(self.bmp)
  28         self.hasShape = self.SetShape(r)
  29 
  30     def OnDoubleClick(self, evt):
  31         if self.hasShape:
  32             self.SetShape(wx.Region())
  33             self.hasShape = False
  34         else:
  35             self.SetWindowShape()
  36 
  37     def OnPaint(self, evt):
  38         dc = wx.PaintDC(self)
  39         dc.DrawBitmap(self.bmp, 0,0, True)
  40 
  41     def OnExit(self, evt):
  42         self.Close()
  43 
  44     def OnLeftDown(self, evt):#2 鼠标按下
  45         self.CaptureMouse()
  46         pos = self.ClientToScreen(evt.GetPosition())
  47         oigin = self.GetPosition()
  48         self.delta = wx.Point(pos.x - oigin.x, pos.y - oigin.y)
  49 
  50     def OnMouseMove(self, evt):#3 鼠标移动
  51         if evt.Dragging() and evt.LeftIsDown():
  52             pos = self.ClientToScreen(evt.GetPosition())
  53             newPos = (pos.x - self.delta.x, pos.y - self.delta.y)
  54             self.Move(newPos)
  55 
  56     def OnLeftUp(self, evt):#4 鼠标释放
  57         if self.HasCapture():
  58             self.ReleaseMouse()
  59 
  60 
  61 
  62 if __name__ == '__main__':
  63     app = wx.PySimpleApp()
  64     ShapedFrame().Show()
  65     app.MainLoop()

#1 我们为三个事件增加了相应的处理器,以作相应的工作。这三个事件是鼠标左键按下,鼠标左键释放和鼠标移动。

#2 拖动事件从鼠标左键按下开始。这个事件处理器做两件事。首先它捕获这个鼠标,直到鼠标被释放,以防止鼠标事件被改善到其它窗口部件。第二,它计算事件发生的位置和窗口左上角之间的偏移量,这个偏移量将被用来计算窗口的新位置。

#3 这个处理器当鼠标移动时被调用,它首先检查看该事件是否是一个鼠标左键按下,如果是,它使用这个新的位置和前面计算的偏移量来确定窗口的新位置,并移动窗口。

#4 当鼠标左键被释放时,ReleaseMouse()被调用,这使得鼠标事件又可以被发送到其它的窗口部件。

这个拖动技术可以被完善以适合其它的需要。例如,仅在一个定义的区域内鼠标敲击才开始一个拖动,你可以对鼠标按下事件的位置做一个测试,使敲击发生在右边的位置时,才能拖动。

使用分割窗

分割窗是一种特殊的容器窗口部件,它管理两个子窗口。这两个子窗口可以被水平的堆放或彼此左右相邻。在两个子窗口之间的是一个窗框,它是一个可移动的边框,移动它就改变了两个子窗口的尺寸。分割窗经常被用于主窗口的侧边栏。图8.10显示了一个分割窗的样板。

当你有两个信息面板并且想让用户自主决定每个面板的尺寸时,可以使用分割窗。Mac OS X Finder窗口就是一个分割窗的例子,并且许多的文本编辑器或制图软件都用它来维护一个打开的文件的列表。

创建一个分割窗

在wxPython中,分割窗是类wx.SplitterWindow的实例。和大多数其它的wxPython窗口部件不一样,分隔窗口在被创建后,可使用前要求进一步的初始化。它的构造函数是十分简单的。

图8.10

w8.10.gif

   1 wx.SplitterWindow(parent, id=-1, pos=wx.DefaultPosition, 
   2         size=wx.DefaultSize, style=wx.SP_3D, 
   3         name="splitterWindow") 

它的这些参数都有标准的含义——parent是窗口部件的容器,pos是窗口部件在它的父容器中位置,size是它的尺寸。

在创建了这个分割窗后,在它可以被使用前,你必须对这个窗口调用三个方法中的一处。如果你想初始时只显示一个子窗口,那么调用Initialize(window),参数window是这个单一的子窗口(通常是一种wx.Panel)。在这种情况下,窗口将在以后响应用户的动作时再分割。

要显示两个子窗口,使用SplitHorizontally (window1,window2,sashPosition=0)或SplitVertically(window1, window2, sashPosition=0)。两个方法的工作都是相似的,参数window1和window2包含两个子窗口,参数sashPosition包含分割条的初始位置。对于水平分割(水平分割条)来说,window1被放置在window2的顶部。如果sashPosition是一个正数,它代表顶部窗口的初始高度(也就是分割条距顶部的像素值)。如果sashPosition是一个负数,它定义了底部窗口的尺寸,或分割条距底部的像素值。如果sashPosition是0,那么这个分割条位于正中。对于垂直分割(垂直分割条),window1位于左边,window2位于右边。正值的sashPosition设置window1的尺寸,也就是分割条距左边框的像素值。类似的,负值sashPosition设置右边子窗口的尺寸,0值将分割条放置在正中。如果你的子窗口复杂的话,我们建议你在布局中使用sizer,以便于当分割条被移动时很好地调整窗口的大小。

一个分割窗的例子

例8.12显示了如何创建有一个子窗口的分割窗并且在以后响应菜单项的分割。这个例子也使用了一些事件,这些事件我们以后讨论。注意,我们不计划在开始可见的子面板使用Hide()方法来隐藏。我们这样做是因为我们开始时不告诉分割窗去管理那个子面板的尺寸和位置,所以我们使用这种方法来隐藏它。如果我们在开始就要分割和显示这两个子面板,那么我们就不必考虑这些。

例8.12 如何创建你自己的分割窗

   1 import wx
   2 
   3 class SplitterExampleFrame(wx.Frame):
   4     def __init__(self, parent, title):
   5         wx.Frame.__init__(self, parent, title=title)
   6         self.MakeMenuBar()
   7         self.minpane = 0
   8         self.initpos = 0
   9         self.sp = wx.SplitterWindow(self)# 创建一个分割窗
  10         self.p1 = wx.Panel(self.sp, style=wx.SUNKEN_BORDER)# 创建子面板
  11         self.p2 = wx.Panel(self.sp, style=wx.SUNKEN_BORDER)
  12         self.p1.SetBackgroundColour("pink")
  13         self.p2.SetBackgroundColour("sky blue")
  14         self.p1.Hide()# 确保备用的子面板被隐藏
  15         self.p2.Hide()
  16 
  17         self.sp.Initialize(self.p1)# 初始化分割窗
  18         
  19         self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING,
  20                   self.OnSashChanging, self.sp)
  21         self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED,
  22                   self.OnSashChanged, self.sp)
  23 
  24 
  25     def MakeMenuBar(self):
  26         menu = wx.Menu()
  27         item = menu.Append(-1, "Split horizontally")
  28         self.Bind(wx.EVT_MENU, self.OnSplitH, item)
  29         self.Bind(wx.EVT_Update_UI, self.OnCheckCanSplit, item)
  30         item = menu.Append(-1, "Split vertically")
  31         self.Bind(wx.EVT_MENU, self.OnSplitV, item)
  32         self.Bind(wx.EVT_Update_UI, self.OnCheckCanSplit, item)
  33         item = menu.Append(-1, "Unsplit")
  34         self.Bind(wx.EVT_MENU, self.OnUnsplit, item)
  35         self.Bind(wx.EVT_Update_UI, self.OnCheckCanUnsplit, item)
  36 
  37         menu.AppendSeparator()
  38         item = menu.Append(-1, "Set initial sash position")
  39         self.Bind(wx.EVT_MENU, self.OnSetPos, item)
  40         item = menu.Append(-1, "Set minimum pane size")
  41         self.Bind(wx.EVT_MENU, self.OnSetMin, item)
  42       
  43         menu.AppendSeparator()
  44         item = menu.Append(wx.ID_EXIT, "E ")
  45         self.Bind(wx.EVT_MENU, self.OnExit, item)
  46 
  47         mbar = wx.MenuBar()
  48         mbar.Append(menu, "Splitter")
  49         self.SetMenuBar(mbar)
  50 
  51         
  52     def OnSashChanging(self, evt):
  53         print "OnSashChanging:", evt.GetSashPosition()
  54     
  55     def OnSashChanged(self, evt):
  56         print "OnSashChanged:", evt.GetSashPosition()
  57     
  58 
  59     def OnSplitH(self, evt):# 响应水平分割请求
  60         self.sp.SplitHorizontally(self.p1, self.p2, self.initpos)
  61     
  62     def OnSplitV(self, evt):# 响应垂直分割请求
  63         self.sp.SplitVertically(self.p1, self.p2, self.initpos)
  64 
  65     def OnCheckCanSplit(self, evt):
  66         evt.Enable(not self.sp.IsSplit())
  67 
  68     def OnCheckCanUnsplit(self, evt):
  69         evt.Enable(self.sp.IsSplit())
  70 
  71     def OnUnsplit(self, evt):
  72         self.sp.Unsplit()
  73     
  74     def OnSetMin(self, evt):
  75         minpane = wx.GetNumberFromUser(
  76             "Enter the minimum pane size",
  77             "", "Minimum Pane Size", self.minpane,
  78             0, 1000, self)
  79         if minpane != -1:
  80             self.minpane = minpane
  81             self.sp.SetMinimumPaneSize(self.minpane)
  82 
  83     def OnSetPos(self, evt):
  84         initpos = wx.GetNumberFromUser(
  85             "Enter the initial sash position (to be used in the Split call)",
  86             "", "Initial Sash Position", self.initpos,
  87             -1000, 1000, self)
  88         if initpos != -1:
  89             self.initpos = initpos
  90     
  91 
  92     def OnExit(self, evt):
  93         self.Close()
  94 
  95 
  96 app = wx.PySimpleApp(redirect=True)
  97 frm = SplitterExampleFrame(None, "Splitter Example")
  98 frm.SetSize((600,500))
  99 frm.Show()
 100 app.SetTopWindow(frm)
 101 app.MainLoop()

分割窗只能分割一次,对已分割的窗口再分割将会失败,从而导致分割方法返回False(成功时返回True)。要确定窗口当前是否被分割了,调用方法IsSplit()。在例8.12中,为了确保相应的菜单项有效,就采用这个方法。

如果你想不分割窗口,那么使用Unsplit(toRemove=None)。参数toRemove是实际要移除的wx.Window对象,并且必须是这两个子窗口中的一个。如果toRemove是None,那么底部或右部的窗口将被移除,这根据分割的方向而定。默认情况下,被移除的窗口是没有被wxPython删除的,所以以后你可以再把它添加回来。unsplit方法在取消分割成功时返回True。如果分割窗当前没有被分割,或toRemove参数不是两个子窗口中的一个,那么该方法返回False。

要确保你对想要的子窗口有一个正确的引用,那么使用GetWindow1()和GetWindow2()方法。GetWindow1()方法返回顶部或左边的子窗口,而GetWindow2()方法返回底部或右边的窗口。由于没有一个直接的设置方法来改变一个子窗口,所以使用方法ReplaceWindow(winOld, winNew),winOld是你要替换的wx.Window对象,winNew是要显示的新窗口。

改变分割的外观

有许多样式标记用来控制显示在屏幕上的分割窗的外观。注意,由于分割与平台有关,所以不是所有列出的标记都将对任何平台起作用。表8.9说明了这些有效的标记。

我们将在接下来的部分看到,你也可以用你的程序来改变分割的显示,以响应用户的动作或你自己的要求。

表8.9 分割窗的样式

wx.SP_3D

绘制三维的边框和分割条。这是一个默认样式。

wx.SP_3DBORDER

绘制三维样式的边框,不包括分割条。

wx.SP_3DSASH

绘制三维样式的分割条,不包括边框。

wx.SP_BORDER

绘制窗口的边框,非三维的样式。

wx.SP_LIVE_Update

改变响应分割条移动的默认行为。如果没有设置这个标记,那么当用户拖动分割条时,将绘制一条线来标明分割条的新位置。子窗口的尺寸没有被实际地更新,直到完成分割条拖放。如果设置了这个标记,那么当分割条在被拖动时,子窗口的尺寸将不断地变化。

wx.SP_NOBORDER

不绘制任何边框。

wx.SP_NO_XP_THEME

在Windows XP系统下,分割条不使用XP的主题样式,它给窗口一个更经典的外观。

wx.SP_PERMIT_UNSPLIT

如果设置了这个样式,那么窗口始终不被分割。如果不设置,你可以通过设置大于0的最小化的窗格尺寸来防止窗口被分割。

以程序的方式处理分割

一旦分割窗被创建,你就可以使用窗口的方法来处理分割条的位置。特别是,你可以使用方法SetSashPosition(position,redraw=True)来移动分割条。position是以像素单位的新的位置,它是分割条距窗口顶部或左边的距离。用在分割方法中的负值,表示位置从底部或右边算起。如果redraw为True,则窗口立即更新。否则它等待常规窗口的刷新。如果你的像素值在范围外的话,设置方法的行为将不被定义。要得到当前分割条的位置,使用GetSashPosition()方法。

在默认的分割行为下,用户可以在两个边框间随意移到分割条。移动分割条到一边,使得别一子窗口的尺寸为0,这导致窗口此时成未分割状态。要防止这种情况,你可以使用方法SetMinimumPaneSize(paneSize)来指定子窗口的最小尺寸。paneSize参数是子窗口的最小像素尺寸。这样,用户就不能通过拖放来使子窗口更小,程序同样也不能使子窗口更小。如前所述,你可以使用wx.SP_PERMIT_UNSPLIT样式来达到相同的效果。要得到当前最小子窗口尺寸,使用方法GetMinimumPaneSize()。

改变窗口的分割模式,使用方法SetSplitMode(mode),参数mode取下列常量值之一:wx.SPLIT_VERTICAL、wx.SPLIT_HORIZONTAL。如果模式改变了,那么顶部窗口变为左边,而底部变为右边(反之亦然)。该方法不引起窗口的重绘,你必须显式地进行强制重绘。你可以使用GetSplitMode()来得到当前的分割模式,它返回上面两个常量值之一。如果窗口当前是未分割状态,那么GetSplitMode()方法返回最近的分割模式。

典型的,如果wx.SP_LIVE_Update样式没有被设置,那么子窗口仅在分割条拖动会话结束时改变尺寸。如果你想在其它时刻强制子窗口重绘,你可以使用方法UpdateSize()。

响应分割事件

分割窗触发wx.SplitterEvent类事件。这儿有四个不同的分割窗的事件类型,如表8.10所示。

表8.10 分割窗的事件类型

EVT_SPLITTER_DCLICK

当分割条被双击时触发。捕捉这个事件不阻塞标准的不分割行为,除非你调用事件的Veto()方法。

EVT_SPLITTER_SASH_POS_CHANGED

分割条的改变结束后触发,但在此之前,改变将在屏幕上显示(因此你可以再作用于它)。这个事件可以使用Veto()来中断。

EVT_SPLITTER_SASH_POS_CHANGING

当分割条在被拖动时,不断触发该事件。这个事件可以通过使用事件的Veto()方法来中断,如果被中断,那么分割条的位置不被改变。

EVT_SPLITTER_UNSPLIT

变成未分割状态时触发。

这个分割事件类是wx.CommandEvent的子类。从分割事件的实例,你可以访问关于分割窗当前状态的信息。对于涉及到分割条移动的两个事件,调用GetSashPosition()得到分割条相对于左或顶部的位置,这依据分割条的方向而定。在位置正在变化事件中,调用SetSashPosition(pos),将用线条表示新的位置。在位置已改变事件中,SetSashPosition(pos)方法将移动分割条。对于双击事件,你可以使用事件的GetX() 和GetY()方法得到敲击的确切位置。对于未分割事件,你可以使用GetWindowBeingRemoved()方法来得到哪个窗口被移除了。

本章小结

1、wxPython中的大部分用户交互都发生在wx.Frame或wx.Dialog中。wx.Frame代表用户调用的窗口。wx.Frame实例的创建就像其它的wxPython窗口部件一样。wx.Frame的典型用法包括创建子类,子类通过定义子窗口部件,布局和行为来扩展基类。通常,一个框架包含只包含一个wx.Panel的顶级子窗口部件或别的容器窗口。

2、这儿还有各种特定于wx.Frame的样式标记。其中的一些影响框架的尺寸和形状,另一些影响在系统中相对于其它的框架,它将如何被绘制,还有一些定义了在框架边框上有那些界面装饰。在某种情况下,定义一个样式标记需要“两步”的创建过程。

3、通过调用Close()方法可以产生关闭框架的请求。这给了框架一个关闭它所占用的资源的机会。框架也能否决一个关闭请求。调用Destroy()方法将迫使框架立即消失而没有任何延缓。

4、框架中的一个特定的子窗口部件可以使用它的wxPython ID、名字或它的文本标签来发现。

5、通过包括wx.ScrolledWindow类的容器部件可以实现滚动。这儿有几个方法来设置滚动参数,最简单的是在滚动窗口中使用sizer,在这种情况下,wxPython自动确定滚动面板的虚拟尺寸(virtual size)。如果想的话,虚拟尺寸可以被手动设置。

6、这儿有一对不同的框架子类,它们允许不同的外观。类wx.MDIParentFrame可以被用来创建MDI,而wx.MiniFrame可以创建一个带有较小标题栏的工具框样式的窗口。使用SetShape()方法,框架可以呈现出非矩形的形状。形状的区域可以被任何位图定义,并使用简单的颜色掩码来决定区域的边缘。非矩形窗口通常没有标准的标题栏,标题栏使得框架可以被拖动, 但这可以通过显式地处理鼠标事件来管理。

7、位于两个子窗口间的可拖动的分割条能够使用wx.SplitterWindow来实现,分割条可以被用户以交互的方式处理,或以编程的方式处理(如果需要的话)。

在下一章,我们将讨论对话框,它的行为类似于框架。

WxPythonInAction/ChapterEight (last edited 2009-12-25 07:18:54 by localhost)