Contents
使用sizer放置窗口部件
本章内容
·理解sizer
·使用sizer分隔窗口部件
·使用sizer的grid系列
·使用box sizer
·看看sizer的实际应用
传统上,在用户界面编程中的最苦恼的一个问题是管理窗口中的窗口部件的实际布局。因为它涉及到与绝对位置直接打交到,这是很痛苦的一件事情。
所以你需要一个结构,它根据预设的模式来决定如何调整和移动窗口部件。目前推荐用来处理复杂布局的方法是使用sizer。sizer是用于自动布局一组窗口部件的算法。sizer被附加到一个容器,通常是一个框架或面板。在父容器中创建的子窗口部件必须被分别地添加到sizer。当sizer被附加到容器时,它随后就管理它所包含的孩子的布局。
使用sizer的好处是很多的。当子窗口部件的容器的尺寸改变时,sizer将自动计算它的孩子的布局并作出相应的调整。同样,如果其中的一个孩子改变了尺寸,那么sizer能够自动地刷新布局。此外,当你想要改变布局时,sizer管理起来很容易。最大的弊端是sizer的布局有一些局限性。但是,最灵活sizer——grid bag和box,能够做你要它们做的几乎任何事。
sizer是什么?
一个wxPython sizer是一个对象,它唯一的目的就是管理容器中的窗口部件的布局。sizer本身不是一个容器或一个窗口部件。它只是一个屏幕布局的算法。所有的sizer都是抽象类wx.Sizer的一个子类的实例。wxPython提供了5个sizer,定义在表11.1中。sizer可以被放置到别的sizer中以达到更灵活的管理。
表11.1 wxPython中预定义的sizer
Grid:一个十分基础的网格布局。当你要放置的窗口部件都是同样的尺寸且整齐地放入一个规则的网格中是使用它。
Flex grid:对grid sizer稍微做了些改变,当窗口部件有不同的尺寸时,可以有更好的结果。
Grid bag:grid sizer系列中最灵活的成员。使得网格中的窗口部件可以更随意的放置。
Box:在一条水平或垂直线上的窗口部件的布局。当尺寸改变时,在控制窗口部件的的行为上很灵活。通常用于嵌套的样式。可用于几乎任何类型的布局。
Static box:一个标准的box sizer。带有标题和环线。
如果你想要你的布局类似grid或box,wxPython可以变通;实际上,任何有效的布局都能够被想像为一个grid或一系列box。
所有的sizer都知道它们的每个孩子的最小尺寸。通常,sizer也允许有关布局的额外的信息,例如窗口部件之间有多少空间,它能够使一个窗口部件的尺寸增加多以填充空间,以及当窗口部件比分配给它们的空间小时如何对齐这些窗口部件等。根据这些少量的信息sizer用它的布局算法来确定每个孩子的尺寸和位置。wxPython中的每种sizer对于同组子窗口部件所产生的最终布局是不同的。
贯穿本章,你都会看到,我们使用非常相似的布局来演示每个sizer类型。
下面是使用一个sizer的三个基本步骤:
创建并关联sizer到一个容器。sizer被关联到容器使用wx.Window的SetSizer(sizer)方法。由于这是一个wx.Window的方法,所以这意味着任何wxPython窗口部件都可以有一个sizer,尽管sizer只对容器类的窗口部件有意义。
添加每个孩子到这个sizer。所有的孩子窗口部件需要被单独添加到该sizer。仅仅创建使用容器作为父亲的孩子窗口部件是不够的。还要将孩子窗口部件添加到一个sizer,这个主要的方法是Add()。Add()方法有一对不同的标记,我们将在下一节讨论。
(可选的)使sizer能够计算它的尺寸。告诉sizer去根据它的孩子来计算它的尺寸,这通过在父窗口对象上调用wx.Window的Fit()方法或该sizer的Fit(window)方法。(这个窗口方法重定向到sizer方法。)两种情况下,这个Fit()方法都要求sizer根据它所掌握的它的孩子的情况去计算它的尺寸,并且它调整父窗口部件到合适的尺寸。还有一个相关的方法:FitInside(),它不改变父窗口部件的显示尺寸,但是它改变它虚拟尺寸——这意味着如果窗口部件是在一个可滚动的面板中,那么wxPython会重新计算是否需要滚动条。
我们既要讨论特定的sizer的行为,也要讨论所有sizer的共同行为。这就有个先后的问题。我们将以介绍grid sizer作为开始,它是最容易理解的。之后,我们将讨论所有sizer的共同行为,使用grid sizer作为一个例子。(使用grid sizer作为例子使得最共同的行为更形象化。)之后我们将讨论其它的特定类型的sizer。
基本的sizer:grid
sizer
后面所有的例子使用了一个有点无聊的窗口部件,目的是占据布局中的空间,这样你可以看到sizer是如何工作的。例11.1给出了该窗口部件的代码,它被本章中的其余的例子导入。从始至终,你将看到它的大量的图片——它基本上是一个带有标签的简单的矩形。
例11.1 块状窗口,在后面的例子中用作一个窗口部件
1 import wx
2 class BlockWindow(wx.Panel):
3 def __init__(self, parent, ID=-1, label="",
4 pos=wx.DefaultPosition, size=(100, 25)):
5 wx.Panel.__init__(self, parent, ID, pos, size,
6 wx.RAISED_BORDER, label)
7 self.label = label
8 self.SetBackgroundColour("white")
9 self.SetMinSize(size)
10 self.Bind(wx.EVT_PAINT, self.OnPaint)
11 def OnPaint(self, evt):
12 sz = self.GetClientSize()
13 dc = wx.PaintDC(self)
14 w,h = dc.GetTextExtent(self.label)
15 dc.SetFont(self.GetFont())
16 dc.DrawText(self.label, (sz.width-w)/2, (sz.height-h)/2)
贯穿本章,我们将使用不同的sizer来在一个框架中放上几个这样的块窗口部件。我们将使用grid sizer作为开始。
什么是grid
sizer?
wxPython提供的最简单的sizer是grid。顾名思义,一个grid sizer把它的孩子放置在一个二维网格中。位于这个sizer的孩子列表中的第一个窗口部件放置在网格的左上角,其余的按从左到右,从上到下的方式排列,直到最后一个窗口部件被放置在网格的右底部。图11.1显示了一个例子,有九个窗口部件被放置在一个3*3的网格中。注意每个部件之间有一些间隙。
图11.1
当你调整grid sizer的大小时,每个部件之间的间隙将随之改变,但是默认情况下,窗口部件的尺寸不会变,并且始终按左上角依次排列。图11.2显示了调整尺寸后的同一窗口。
图11.2
例11.2显示了用于产生图11.1和11.2的代码。
例11.2 使用grid sizer
1 import wx
2 from blockwindow import BlockWindow
3 labels = "one two three four five six seven eight nine".split()
4 class GridSizerFrame(wx.Frame):
5 def __init__(self):
6 wx.Frame.__init__(self, None, -1, "Basic Grid Sizer")
7 sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)#创建grid sizer
8 for label in labels:
9 bw = BlockWindow(self, label=label)
10 sizer.Add(bw, 0, 0)#添加窗口部件到sizer
11 self.SetSizer(sizer)#把sizer与框架关联起来
12 self.Fit()
13 app = wx.PySimpleApp()
14 GridSizerFrame().Show()
15 app.MainLoop()
你可以从例11.2看到,一个grid sizer是类wx.GridSizer的一个实例。构造函数显式地设置四个属性,这些属性是grid sizer独一无二的:
1 wx.GridSizer(rows, cols, vgap, hgap)
这个构造函数中的rows和cols是整数,它们指定了网格的尺寸——所能放置的窗口部件的数量。如果这两个参数之一被设置为0,那么它的实际的值根据sizer中的孩子的数量而定。例如如果使用wx.GridSizer(2, 0, 0, 0),且sizer有八个孩子,那么它就需要有四列来填充这些孩子。
vgap和hgap使你可以决定窗口控件间的间隔的多少。vgap是两相邻列间的间隔的象素量,hgapvgap是两相邻行间的间隔的象素量。这些象素量是除了窗口控件边框的量。属性rows, cols, vgap, hgap都有各自的get*和set*方法——GetRows(), SetRows(rows), GetCols(),SetCols(cols), GetVGap(), SetVGap(gap), GetHGap(), 和SetHGap(gap) 。
grid sizer的尺寸和位置的算法是十分简单的。当Fit()第一次被调用时,创建初始化的网格布局。如果有必要,行和列的数量根据列表中元素的数量来计算。在grid中每个空格的尺寸是相同的——即使每个窗口部件的尺寸不同。这个最大的尺度是根据网格中宽度最宽的孩子的宽度和高度最高的孩子的高度来计算的。所以,grid sizer最适合用于所有孩子相同尺寸的情况。有着不同尺寸窗口部件的grid sizer看起来有点怪异。如果你仍想要一个类似grid的布局,但是你又有不同尺寸的窗口部件的话,那么可以使用flex grid sizer或grid bag sizer。
如何对sizer添加或移除孩子?
添加孩子部件到sizer中的次序是非常重要的。这与将孩子添加到一个父窗口部件中的通常情况是不一样的。sizer的通常的布局算法要求一次添加一个孩子,以便于决定它们的显示位置。下一项的位置是依赖于前一被添加项的位置的。例如,grid sizer基于窗口部件的次序来从左到右,从上到下的添加并显示。在多数情况下,当你在父窗口部件的构造器中创建sizer时,你将会按正确的次序添加这些项目。但是有时候,如果你在运行时动态地改变你的布局,那么你需要更灵活和更细致。
使用Add()方法
添加一个窗口部件到一个sizer中的最常用的方法是Add(),它将新的窗口部件添加到sizer的孩子列表的尾部。“添加到sizer的孩子列表的尾部”的准确的意思信赖于该sizer的类型,但是通常它意味这个新的窗口部件将依次显示在右下位置。Add()方法有三个不同的样式:
第一个版本是你最常要用到的,它使你能够将一个窗口部件添加到sizer。
第二个版本用于将一个sizer嵌套在另一个中——这最常用于box sizer,但可用于任何类型的sizer。
第三个版本使你能够添加一个wx.Size对象的空的空白尺寸或一个(宽,高)元组到sizer,通常用作一个分隔符(例如,在一个工具栏中)。另外,这在box sizer中最常使用,但也可在任何sizer中使用以用于形成窗口的一个空白区域或分隔不同的窗口部件。
其它的参数影响sizer中的项目如何显示。其中的一些只对某种sizer有效。proportion仅被box sizer使用,并当父窗口尺寸改变时影响一个项目如何被绘制。这个稍后我们将在box sizer时讨论。
flag参数用于放置位标记,它控制对齐、边框和调整尺寸。这些项将在后面的章节中讨论。如果在flag参数中指定了边框,那么border参数包含边框的宽度。如果sizer的算法需要,userData参数可被用来传递额外的数据。如果你正在设计一个自定义的sizer,那么你可以使用该参数。
使用insert()方法
这里有用于将新的窗口部件插入到sizer中不同位置的方法。insert()方法使你能够按任意的索引来放置新的窗口部件。它也有三个形式:
使用Prepend()方法
该方法将新的窗口部件、sizer或空白添加到sizer的列表的开头,这意味所添加的东西将被显示到左上角:
图11.3显示了在例11.1中如果Add()替换为Prepend()后的布局。
图11.3
如果sizer已在屏幕上显示了,而你又要给sizer添加一个新的项目,那么你需要调用sizer的Layout()方法来迫使sizer自己重新排列,以容纳新的项。
使用Detach()方法
为了从sizer中移除一项,你需要调用Detach()方法,它从sizer中移除项目,但是没有销毁该项目。这对于你以后再使用它是有用的。使用Detach()有三种方法。你可以将你想要移除的窗口、sizer对象、对象的索引作为参数传递给Detach():
在这三种情况中,Detach()方法返回一个布尔值,它表明项目是否真的被删除了——如果你试图移除sizer中没有的项,将返回false。和你曾见过的其它的删除方法不同,Detach()不返回被删除的项目,所以如果你想要得到它的话,你需要之前用一个变量来存储对它的引用。
从sizer中删除项目,不会自动改变在屏幕上的显示。你需要调用Layout()方法来执行重绘。
你可以得到一个包含了窗口的sizer的引用,这通过使用wx.Window的GetContainingSizer()方法。如果该窗口部件没有被包含在sizer中,那么该方法返回None。
sizer是如何管理它的孩子的尺寸和对齐的?
当一个新的项目被添加到一个sizer时,sizer就使用这个项目的初始尺寸或根据它的布局计算给出恰当的尺寸(如果它的初始尺寸没有设置)。换句话说,sizer不调整一个项目的大小,除非要求,这通常发生在一个窗口尺寸的改变时。
当sizer的父窗口部件改变了尺寸时,sizer需要改变它的组分的尺寸。默认情况下,sizer保持这些窗口部件的对齐方式不变。
当你添加一个窗口部件到sizer时,可以通过给flag参数一个特定值来调整该窗口部件的尺寸改变行为。图11.4展示了在用户放大窗口后,几个不同标记应用于这个基本的grid sizer的结果。
图11.4
例11.3显示了产生图11.4的代码。除了在窗口部件被添加到sizer时应用了一个标记字典外,其它的与前一个例子相同。
例11.3 使用了用于对齐和调整尺寸的标记的一个grid sizer
1 import wx
2 from blockwindow import BlockWindow
3 labels = "one two three four five six seven eight nine".split()
4 #对齐标记
5 flags = {"one": wx.ALIGN_BOTTOM, "two": wx.ALIGN_CENTER,
6 "four": wx.ALIGN_RIGHT, "six": wx.EXPAND, "seven": wx.EXPAND,
7 "eight": wx.SHAPED}
8 class TestFrame(wx.Frame):
9 def __init__(self):
10 wx.Frame.__init__(self, None, -1, "GridSizer Resizing")
11 sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
12 for label in labels:
13 bw = BlockWindow(self, label=label)
14 flag = flags.get(label, 0)
15 sizer.Add(bw, 0, flag)
16 self.SetSizer(sizer)
17 self.Fit()
18 app = wx.PySimpleApp()
19 TestFrame().Show()
20 app.MainLoop()
在这个例子中,窗口部件“one,” “two,” 和“four”分别使用wx.ALIGN_BOTTOM, wx.ALIGN_CENTER, and wx.ALIGN_RIGHT标记改变它们的对齐方式。当窗口大小改变时,你可以看到效果,部件“three"没有指定一个标记,所以它按左上角对齐。窗口"six"和"seven"均使用了wx.EXPAND标记来告诉sizer改变它们的尺寸以填满格子,而窗口部件"eight"使用wx.SHAPED来改变它的尺寸,以保持比例不变。
表11.2显示与尺寸调整和对齐相关的flag的值。
表11.2 尺寸调整和对齐行为标记
wx.ALIGN_BOTTOM |
按照窗口部件被分配的空间(格子)的底部对齐。 |
wx.ALIGN_CENTER |
放置窗口部件,使窗口部件的中心处于其所分配的空间的中心。 |
wx.ALIGN_CENTER_HORIZONTAL |
在它所处的格子中,水平居中。 |
wx.ALIGN_CENTER_VERTICAL |
在它所处的格子中,垂直居中。 |
wx.ALIGN_LEFT |
靠着它所处的格子左边缘。这是默认行为。 |
wx.ALIGN_TOP |
靠着它所处的格子的上边缘。这是默认的行为。 |
wx.EXPAND |
填满它所处的格子空间。 |
wx.FIXED_MINSIZE |
保持固定项的最小尺寸。 |
wx.GROW |
与wx.EXPAND相同。但比之少两个字符,节约了时间。 |
wx.SHAPED |
窗口部件的尺寸改变时,只在一个方向上填满格子,另一个方向上按窗口部件原先的形状尺寸的比列填充。 |
这些标记可以使用|来组合,有时,这些组合会很有意思。wx.ALIGN_TOP | wx.ALIGN_RIGHT使得窗口部件位于格子的右上角。(注意,互相排斥的标记组合如wx.ALIGN_TOP | wx.ALIGN_BOTTOM中,默认的标记不起作用,这是因为默认标记的相应位上是0,在或操作中没有什么影响)。
还有一些方法,你可以用来在运行时处理sizer或它的孩子的尺寸和位置。你可以使用方法GetSize()和 GetPosition()来得到sizer的当前尺寸和位置——这个位置是sizer相对于它所关联的容器的。如果sizer嵌套在另一个sizer中,那么这些方法是很有用的。你可以通过调用SetDimension(x, y, width, height)方法来指定一个sizer的尺寸,这样sizer将根据它的新尺寸和位置重新计算它的孩子的尺寸。
能够为sizer或它的孩子指定一个最小的尺寸吗?
sizer的窗口部件的布局中的另一个重要的要素是为sizer或它的孩子指定一个最小尺寸的能力。一般你不想要一个控件或一个sizer小于一个特定的尺寸,通常因为这样会导致文本被窗口部件的边缘截断。或在一个嵌套的sizer中,控件在窗口中不能被显示出来。为了避免诸如此类的情况,你可以使用最小尺寸。
图11.5显示了对一个特定的窗口部件设置最小尺寸的一个例子。该窗口的尺寸已被用户改变了。
图11.5
例11.4展示了产生该图的代码。它类似于基本的grid的代码,其中增加了一个SetMinSize()调用。
例11.4 使用最小尺寸设置的grid sizer
1 import wx
2 from blockwindow import BlockWindow
3 labels = "one two three four five six seven eight nine".split()
4 class TestFrame(wx.Frame):
5 def __init__(self):
6 wx.Frame.__init__(self, None, -1, "GridSizer Test")
7 sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
8 for label in labels:
9 bw = BlockWindow(self, label=label)
10 sizer.Add(bw, 0, 0)
11 center = self.FindWindowByName("five")
12 center.SetMinSize((150,50))
13 self.SetSizer(sizer)
14 self.Fit()
15 app = wx.PySimpleApp()
16 TestFrame().Show()
17 app.MainLoop()
当一个sizer被创建时,它根据它的孩子的综合的最小尺寸(最小的宽度和最小的高度)隐含地创建一个最小尺寸。多数控件都知道它们最小化的“最佳尺寸”,sizer查询该值以确定默认的布局。如果显式地使用一个尺寸值来创建一个控件,那么这个设置的尺寸值覆盖所计算出的最佳尺寸。一个控件的最小尺寸可以使用窗口的方法SetMinSize(width, height)和SetSizeHints(minW, minH, maxW, maxH)来设置——第二个方法使你也能够指定一个最大尺寸。如果一个窗口部件的属性(通常是所显示的字体或文本标签)改变,该窗口部件通常将调整它的最佳尺寸。
如果一个窗口有相关的sizer,那么这个容器窗口的最佳尺寸由它的sizer来确定。如果没有,那么该窗口的最佳尺寸就是足够大到显示所有子控件的尺寸。如果该窗口没有孩子,那么它使用所设置的最小尺寸为最佳尺寸。如果上述都没有,那么该容器窗口的当前尺寸作为其最佳尺寸。
你可以使用GetMinSize()来访问整个sizer的最小尺寸。如果你想为整个sizer设置一个较大的最小尺寸,那么你可以使用SetMinSize(width,height),该函数也可以使用一个wx.Size实例作为参数——SetMinSize(size),尽管在wxPython中你很少显式地创建一个wx.Size。在最小尺寸已被设置后,GetMinSize()返回设置的尺寸或孩子的综合的尺寸。
如果你只想设置sizer内的一个特定的孩子的最小尺寸,那么使用sizer的SetItemMinSize()方法。它也有三个形式:
这里,参数window和sizer必须是一个sizer实例的孩子。如果需要的话,方法将在整个嵌套树中搜索特定的子窗口或子sizer。参数index是sizer的孩子列表中的索引。参数size是一个wx.Size对象或一个(宽,高)元组,它是sizer中的项目被设置的最小尺寸。如果你设置的最小尺寸比窗口部件当前的尺寸大,那么它自动调整。你不能根据sizer来设置最大尺寸,只能根据窗口部件来使用SetSizeHints()设置。
sizer如何管理每个孩子的边框?
wxPython sizer能够使它的一个或所有孩子有一个边框。边框是连续数量的空白空间,它们分离相邻的窗口部件。当sizer计算它的孩子的布置时,边框的尺寸是被考虑进去了的,孩子的尺寸不会小于边框的宽度。当sizer调整尺寸时,边框的尺寸不会改变。
图11.6显示了一个10像素的边框。在每行中,中间的元素四边都有边框围绕,而其它的只是部分边有边框。增加边框不会使窗口部件更小,而是使得框架更大了。
图11.6
例11.5是产生图11.6的相关代码。它和基本的grid sizer相似,只是我们增加了一个边框值字典,并给Add()一个10像素的边框。
例11.5 使用边框设置的grid sizer代码
1 import wx
2 from blockwindow import BlockWindow
3 labels = "one two three four five six seven eight nine".split()
4 #边框标记
5 flags = {"one": wx.BOTTOM, "two": wx.ALL, "three": wx.TOP,
6 "four": wx.LEFT, "five": wx.ALL, "six": wx.RIGHT,
7 "seven": wx.BOTTOM | wx.TOP, "eight": wx.ALL,
8 "nine": wx.LEFT | wx.RIGHT}
9 class TestFrame(wx.Frame):
10 def __init__(self):
11 wx.Frame.__init__(self, None, -1, "GridSizer Borders")
12 sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
13 for label in labels:
14 bw = BlockWindow(self, label=label)
15 flag = flags.get(label, 0)
16 sizer.Add(bw, 0, flag, 10)#添加指定边框的窗口部件
17 self.SetSizer(sizer)
18 self.Fit()
19 app = wx.PySimpleApp()
20 TestFrame().Show()
21 app.MainLoop()
要在一个sizer中的窗口部件周围放置边框,需要两步。第一步是当窗口部件被添加到该sizer时,传递额外的标记给flags参数。你可以使用标记wx.ALL来指定边框围绕整个窗口部件,或使用wx.BOTTOM, wx.LEFT, wx.RIGHT, wx.TOP来指定某一边有边框。这些标记当然可以组合成你想要的,如wx.RIGHT | wx.BOTTOM将使你的窗口部件的右边和底边有边框。由于边框、尺寸调整、对齐这些信息都是经由flags参数,所以对于同一个窗口部件,你通常必须将三种标记组合起来使用。
在你传递了边框信息到flags参数后,你也需要传递边框宽度的像素值给border参数。例如,下面的调用将添加窗口部件到sizer列表的尾部,并使该窗口部件的周围有5个像素宽度的边框:
1 sizer.Add(widget, 0, wx.ALL | wx.EXPAND, 5)
该部件然后将扩展以填充它的有效空间,且四周始终留有5个像素的空白。
使用其它类型的sizer
我们已经讨论了基本的sizer,现在我们可以转向更复杂和更灵活的sizer了。其中两个(flex grid sizer和grid bag sizer)本质上是grid的变种。另外两个(box和static box sizer)使用一个不同的和更灵活的布局结构。
什么是flex grid sizer?
flex grid sizer是grid sizer的一个更灵活的版本。它与标准的grid sizer几乎相同,除了下面的例外:
1、每行和每列可以有各自的尺寸。
2、默认情况下,当尺寸调整时,它不改变它的单元格的尺寸。如果需要的话,你可以指定哪行或哪列应该增长。
3、它可以在两个方向之一灵活地增长,意思是你可以为个别的子元素指定比列量,并且你可以指定固定方向上的行为。
图11.7显示了一个flex grid sizer,它的布局也是9个单元格。这里的中间单元格更大。
图11.7
与图11.5相比较,对于相同的布局,图11.5中每个单元格的尺寸与中间对象的相同,在flex grid sizer中,单元格的尺寸大小根据它所在的行和列来定。它们宽度是该列中宽度最大的项目的宽度,它们的高度是该行中宽度最高的项目的宽度。在这里,项目“four”和项目“six”的单元格的高度比项目本身的高度更高,因为其同行中的项目“five”,而"two"和"seven"的单元格的宽度也更宽。“one,” “three,” “seven,” 和“nine”的单元格是正常的尺寸,并且不受较大的窗口部件的影响。
图11.8展示了当调整窗口尺寸时,flex grid sizer的默认行为——单元格的尺寸不改变。
图11.8
例11.6显示了产生了图11.8的代码
例11.6 创建一个flex grid sizer
1 import wx
2 from blockwindow import BlockWindow
3 labels = "one two three four five six seven eight nine".split()
4 class TestFrame(wx.Frame):
5 def __init__(self):
6 wx.Frame.__init__(self, None, -1, "FlexGridSizer")
7 sizer = wx.FlexGridSizer(rows=3, cols=3, hgap=5, vgap=5)
8 for label in labels:
9 bw = BlockWindow(self, label=label)
10 sizer.Add(bw, 0, 0)
11 center = self.FindWindowByName("five")
12 center.SetMinSize((150,50))
13 self.SetSizer(sizer)
14 self.Fit()
15 app = wx.PySimpleApp()
16 TestFrame().Show()
17 app.MainLoop()
一个flex grid sizer是wx.FlexGridSizer的一个实例。类wx.FlexGridSizer是wx.GridSizer的子类,所以wx.GridSizer的属性方法依然有效。wx.FlexGridSizer的构造函数与其父类的相同:
wx.FlexGridSizer(rows, cols, vgap, hgap)
为了当sizer扩展时,使一行或列也扩展,你需要使用适当的方法显式地告诉该sizer该行或列是可扩展的:
当sizer水平扩展时,关于新宽度的默认行为被等同地分配给每个可扩展的列。同样,一个垂直的尺寸调整也被等同地分配给每个可扩展的行。要改变这个默认的行为并且使不同的行和列有不现的扩展比率,你需要使用proportion参数。如果proportion参数被使用了,那么与该参数相关的新的空间就被分配给了相应的行或列。例如,如果你有两个尺寸可调整的行,并且它们的proportion分别是2和1,那么这第一个行将得到新空间的2/3,第二行将得到1/3。图11.9显示使用proportional(比列)空间的flex grid sizer。在这里,中间行和列所占的比例是2和5,两端的行和列所占的比例是1。
图11.9
正如你可以看到的,当所有的单元格增大时,中间的行和列的增大是两端的两倍。窗口部件的没有改变尺寸以填表充它们的单元格,虽然可以通过在当它们被添加到sizer时使用wx.EXPAND来实现。例11.7显示了产生图11.9的代码。
例11.7
1 import wx
2 from blockwindow import BlockWindow
3 labels = "one two three four five six seven eight nine".split()
4 class TestFrame(wx.Frame):
5 def __init__(self):
6 wx.Frame.__init__(self, None, -1, "Resizing Flex Grid Sizer")
7 sizer = wx.FlexGridSizer(rows=3, cols=3, hgap=5, vgap=5)
8 for label in labels:
9 bw = BlockWindow(self, label=label)
10 sizer.Add(bw, 0, 0)
11 center = self.FindWindowByName("five")
12 center.SetMinSize((150,50))
13 sizer.AddGrowableCol(0, 1)
14 sizer.AddGrowableCol(1, 2)
15 sizer.AddGrowableCol(2, 1)
16 sizer.AddGrowableRow(0, 1)
17 sizer.AddGrowableRow(1, 5)
18 sizer.AddGrowableRow(2, 1)
19 self.SetSizer(sizer)
20 self.Fit()
21 app = wx.PySimpleApp()
22 TestFrame().Show()
23 app.MainLoop()
如果你对一个可扩展的行或列使用了比例尺寸,那么你需要对该方向上的所有可扩展的行或列指定一个比例量,否则你将得到一个糟糕的效果。
在flex grid sizer中还有另外一个机制用于控制窗口部件的增长(执不执行先前AddGrowable*方法的设置)。默认情况下,比例尺寸适用于flex grid的两个方向;但是你可以通过使用SetFlexibleDirection(direction)方法来指定仅某个方向应该按比例调整尺寸,参数direction的值可以是:wx.HORIZONTAL, wx.VERTICAL, 或wx.BOTH (默认值)。然后你可以使用SetNonFlexibleGrowMode(mode)方法来指定另一个方向上的行为。例如,如果你调用了SetFlexibleDirection(wx.HORIZONTAL)方法,列的行为就遵循AddGrowableCol(),然后调用SetNonFlexibleGrowMode()来定义行的行为。表11.3显示了mode参数的有效值。
表11.3
wx.FLEX_GROWMODE_ALL:flex grid在没有使用SetFlexibleDirection*的方向上等同地调整所有单元格的尺寸。这将覆盖使用AddGrowable*方法设置的任何行为——所有的单元格都将被调整尺寸,不管它们的比例或它们是否被指定为可扩展(增长)的。
wx.FLEX_GROWMODE_NONE:在没有使用SetFlexibleDirection*的方向上的单元格的尺寸不变化,不管它们是否被指定为可增长的。
wx.FLEX_GROWMODE_SPECIFIED:在没有使用SetFlexibleDirection*的方向上,只有那些可增长的单元格才增长。但是sizer将忽略任何的比例信息并等同地增长那些单元格。这是一个默认行为。
上面段落中所讨论的SetFlexibleDirection和SetNonFlexibleGrowMode方法都有对应的方法:GetFlexibleDirection()和GetNonFlexibleGrowMode(),它们返回整型标记。在上表中要强调的是,任何使用这些方法来指定的设置将取代通过AddGrowableCol()和AddGrowableRow()创建的设置。
什么是grid bag sizer?
grid bag sizer是对flex grid sizer进一步的增强。在grid bag sizer中有两个新的变化:
1、能够将一个窗口部件添加到一个特定的单元格。 2、能够使一个窗口部件跨越几个单元格(就像HTML表单中的表格所能做的一样)。
图11.10
图11.10显示了一个grid bag sizer的示例。它与本章前面的例子很相似,只是增加了新的窗口部件以展示跨行和跨列。
例11.8显示了产生图11.10的代码。注意这里的Add()方法与以前的看起来有点不同。
例11.8 Grid bag sizer示例代码
#coding=utf-8 #!python import wx from blockwindow import BlockWindow labels = "one two three four five six seven eight nine".split() class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "GridBagSizer Test") sizer = wx.GridBagSizer(hgap=5, vgap=5) for col in range(3): for row in range(3): bw = BlockWindow(self, label=labels[row*3 + col]) sizer.Add(bw, pos=(row,col)) # 跨行 bw = BlockWindow(self, label="span 3 rows") sizer.Add(bw, pos=(0,3), span=(3,1), flag=wx.EXPAND) # 跨列 bw = BlockWindow(self, label="span all columns") sizer.Add(bw, pos=(3,0), span=(1,4), flag=wx.EXPAND) # 使最后的行和列可增长 sizer.AddGrowableCol(3) sizer.AddGrowableRow(3) self.SetSizer(sizer) self.Fit() app = wx.PySimpleApp() TestFrame().Show() app.MainLoop()
grid bag sizer是wx.GridBagSizer的实例,wx.GridBagSizer是wx.FlexGridSizer的一个子类。这意味着所有flex grid sizer的属性,grid bag sizer都适用。
wx.GridBagSizer的构造函数与它的父类有点不同:
1 wx.GridBagSizer(vgap=0, hgap=0)
在一个grid bag sizer中,你不必去指定行和列的数量,因为你可以直接将子项目添加进特定的单元格——sizer将据此计算出网格的尺度。
在grid bag sizer上使用Add()方法
对于grid bag sizer,Add()方法与别的sizer不同。它有四个可选的形式:
1 Add(window, pos, span=wx.DefaultSpan, flag=0, border=0, userData=None) 2 Add(sizer, pos, span=wx.DefaultSpan, flag=0, border=0, userData=None) 3 Add(size, pos, span=wx.DefaultSpan, flag=0, border=0, userData=None) 4 AddItem(item)
这些看起来应该很熟悉,在运行上也与通常的sizer的方法相似。window, sizer, size, flag, border, 和userData参数的行为与通常sizer的方法中的是相同的。pos参数代表sizer中的窗口部件要赋予的单元格。技术上讲,pos参数是类wx.GBPosition的一个实例,但是通过wxPython变换,你可以仅传递一个(行,列)形式的元组,grid bag的左上角是(0,0)。
同样,span参数代表窗口部件应该占据的行和列的数量。它是类wx.GBSpan的一个实例,但是,wxPython也使你能够传递一个(行的范围,列的范围)形式的元组。如果跨度没有指定,那么默认值是(1,1),这意味该窗口部件在两个方向都只能占据一个单元格。例如,要在第二行第一列放置一个窗口部件,并且使它占据三行两列,那么你将这样调用:Add(widget, (1, 0), (3, 2))(索引是从0开始的)。
Additem方法的item参数是类wx.GBSizerItem的一个实例,它包含了grid bag sizer放置项目所需要的全部信息。你不太可能需要直接去创建一个wx.GBSizerItem。如果你想去创建一个,那么它的构造函数的参数与grid bag sizer的其它Add()方法相同。一旦你有了一个wx.GBSizerItem,这儿有许多的get*方法使你能够访问项目的属性,也许这最有用的是GetWindow(),它返回实际显示的窗口部件。
由于项目是使用行和列的索引以及跨度被添加到一个grid bag sizer的,所以项目被添加的顺序不必按照其它sizer所要求的对应它们的显示顺序。这使得跟踪哪个项实际显示在哪个单元格有一点头痛。表11.4列出了几个方法,grid bag sizer通过它们来使你对项目的跟踪较为容易。
表11.4 Grid bag sizer 管理项目的方法
CheckForIntersection(item,excludeItem=None) CheckForIntersection(pos,span, excludeItem=None):将给定的项目或给定的位置和跨度同sizer中的项目进行比对。如果有任一项与给定项目的位置或给定的位置和跨度重叠,则返回True。excludeItem是一个可选的项,它不被包括在比对中(或许是因为它正在测试中)。pos参数是一个wx.GBPosition或一个元组。span参数是一个wx.GPSpan或一个元组。
FindItem(window) FindItem(sizer):返回对应于给定的窗口或sizer的wx.GBSizerItem。如果窗口或sizer不在grid bag中则返回None。这个方法不递归检查其中的子sizer。
FindItemAtPoint(pt):pt参数是对应于所包含的框架的坐标的一个wx.Point实例或一个Python元组。这个方法返回位于该点的wx.GBSizerItem 。如果这个位置在框架的边界之外或如果该点没有sizer项目,则返回None。
FindItemAtPosition(pos):该方法返回位于给定单元格位置的wx.GBSizerItem,参数pos是一个wx.GBPosition或一个Python元组。如果该位置在sizer的范围外或该位置没有项目,则返回None。
FindItemWithData(userData):返回grid bag中带有给定的userData对象的一个项目的wx.GBSizerItem。
Grid bag也有一对能够用于处理单元格尺寸和项目位置的属性。在grid bag被布局好并显示在屏幕上后,你可以使用方法GetCellSize(row, col)来获取给定的单元格显示在屏幕上的尺寸。这个尺寸包括了由sizer本身所管理的水平和垂直的间隔。你可以使用方法GetEmptyCellSize()得到一个空单元格的尺寸,并且你可以使用SetEmptyCellSize(sz)改变该属性,这里的sz是一个wx.Size对象或一个Python元组。
你也可以使用方法GetItemPosition()和GetItemSpan()来得到grid bag中的一个对象的位置或跨度。这两个方法要求一个窗口,一个sizer或一个索引作为参数。这个索引参数与sizer的Add()列表中的索引相对应,这个索引在grid bag的上下文中没多大意思。上面的两个get*方法都有对应的set*方法,SetItemPosition(window, pos)和SetItemSpan(window, span),这两个方法的第一个参数可以是window,sizer,或index,第二个参数是一个Python元组或一个wx.GBPosition或wx.GBSpan对象。
什么是box
sizer?
box sizer是wxPython所提供的sizer中的最简单和最灵活的sizer。一个box sizer是一个垂直列或水平行,窗口部件在其中从左至右或从上到下布置在一条线上。虽然这听起来好像用处太简单,但是来自相互之间嵌套sizer的能力使你能够在每行或每列很容易放置不同数量的项目。由于每个sizer都是一个独立的实体,因此你的布局就有了更多的灵活性。对于大多数的应用程序,一个嵌套有水平sizer的垂直sizer将使你能够创建你所需要的布局。
图11.11-11.14展示了几个简单的box sizer的例子。图中所展示的各个框架我们都是手动调整了大小的,以便展示每个sizer是如何响应框架的增大的。图11.11显示了一个水平的box sizer,图11.12在一个垂直的box sizer显示了现图11.11相同的窗口部件。
图11.11
图11.12
图11.13显示了一个垂直的sizer,其中一个窗口部件被设置成可扩展并填充有效的垂直空间。图11.14展示了一个垂直的sizer,其中的两个窗口部件设置为按不同的比例占据有效的垂直空间。
图11.13
图11.14
生成以上四个sizer框架的示例代码如例11.9所示。
例11.9 产生多个box sizer
1 import wx
2 from blockwindow import BlockWindow
3 labels = "one two three four".split()
4 class TestFrame(wx.Frame):
5 title = "none"
6 def __init__(self):
7 wx.Frame.__init__(self, None, -1, self.title)
8 sizer = self.CreateSizerAndWindows()
9 self.SetSizer(sizer)
10 self.Fit()
11 class VBoxSizerFrame(TestFrame):
12 title = "Vertical BoxSizer"
13 def CreateSizerAndWindows(self):
14 sizer = wx.BoxSizer(wx.VERTICAL)
15 for label in labels:
16 bw = BlockWindow(self, label=label, size=(200,30))
17 sizer.Add(bw, flag=wx.EXPAND)
18 return sizer
19 class HBoxSizerFrame(TestFrame):
20 title = "Horizontal BoxSizer"
21 def CreateSizerAndWindows(self):
22 sizer = wx.BoxSizer(wx.HORIZONTAL)
23 for label in labels:
24 bw = BlockWindow(self, label=label, size=(75,30))
25 sizer.Add(bw, flag=wx.EXPAND)
26 return sizer
27 class VBoxSizerStretchableFrame(TestFrame):
28 title = "Stretchable BoxSizer"
29 def CreateSizerAndWindows(self):
30 sizer = wx.BoxSizer(wx.VERTICAL)
31 for label in labels:
32 bw = BlockWindow(self, label=label, size=(200,30))
33 sizer.Add(bw, flag=wx.EXPAND)
34 # Add an item that takes all the free space
35 bw = BlockWindow(self, label="gets all free space", size=(200,30))
36 sizer.Add(bw, 1, flag=wx.EXPAND)
37 return sizer
38 class VBoxSizerMultiProportionalFrame(TestFrame):
39 title = "Proportional BoxSizer"
40 def CreateSizerAndWindows(self):
41 sizer = wx.BoxSizer(wx.VERTICAL)
42 for label in labels:
43 bw = BlockWindow(self, label=label, size=(200,30))
44 sizer.Add(bw, flag=wx.EXPAND)
45 # Add an item that takes one share of the free space
46 bw = BlockWindow(self,
47 label="gets 1/3 of the free space",
48 size=(200,30))
49 sizer.Add(bw, 1, flag=wx.EXPAND)
50 # Add an item that takes 2 shares of the free space
51 bw = BlockWindow(self,
52 label="gets 2/3 of the free space",
53 size=(200,30))
54 sizer.Add(bw, 2, flag=wx.EXPAND)
55 return sizer
56 app = wx.PySimpleApp()
57 frameList = [VBoxSizerFrame, HBoxSizerFrame,
58 VBoxSizerStretchableFrame,
59 VBoxSizerMultiProportionalFrame]
60 for klass in frameList:
61 frame = klass()
62 frame.Show()
63 app.MainLoop()
box sizer是类wx.BoxSizer的实例,wx.BoxSizer是wx.Sizer的子类,相对于wx.Sizer,wx.BoxSizer几乎没有增加新的方法。wx.BoxSizer的构造函数有一个参数:
1 wx.BoxSizer(orient)
参数orient代表该sizer的方向,它的取值可以是wx.VERTICAL或wx.HORIZONTAL。对于box sizer所定义的唯一一个新的方法是GetOrientation(),它返回构造函数中orient的整数值。一旦一个box sizer被创建后,你就不能改变它的方向了。box sizer的其它的函数使用本章早先所讨论的一般的sizer的方法。
box sizer的布局算法对待该sizer的主方向(当构建的时候已被它的方向参数所定义)和次要方向是不同的。特别地,proportion参数只适用于当sizer沿主方向伸缩时,而wx.EXPAND标记仅适用于当sizer的尺寸在次方向上变化时。换句话说,当一个垂直的box sizer被垂直地绘制时,传递给每个Add()方法调用的参数proportion决定了每个项目将如何垂直地伸缩。除了影响sizer和它的项目的水平增长外,参数proportion以同样的方式影响水平的box sizer。在另一方面,次方向的增长是由对项目所使用的wx.EXPAND标记来控制的,所以,如果它们设置了wx.EXPAND标记的话,在一个垂直的box sizer中的项目将只在水平方向增长。否则这些项目保持它们的最小或最合适的尺寸。图6.7演示了这个过程。
在box sizer中,项目的比例增长类似于flex grid sizer,但有一些例外。第一,box sizer的比例行为是在窗口部件被添加到该sizer时,使用proportion参数被确定的——你无需像flex grid sizer那样单独地指定它的增长性。第二,比例为0的行为是不同的。在box sizer中,0比例意味着该窗口部件在主方向上不将根据它的最小或最合适尺寸被调整尺寸,但是如果wx.EXPAND标记被使用了的话,它仍可以在次方向上增长。当box sizer在主方向上计算它的项目的布局时,它首先合计固定尺寸的项目所需要的空间,这些固定尺寸的项目,它们的比例为0。余下的空间按项目的比例分配。
什么是static
box sizer?
一个static box sizer合并了box sizer和静态框(static box),静态框在sizer的周围提供了一个漂亮的边框和文本标签。图11.15显示了三个static box sizer。
图11.15
例11.10显示了产生上图的代码。这里有三个值得注意的事件。首先你必须单独于sizer创建静态框对象,第二是这个例子展示了如何使用嵌套的box sizer。本例中,三个垂直的static box sizer被放置于一个水平的box sizer中。
例11.10 static box sizer的一个例子
1 import wx
2 from blockwindow import BlockWindow
3 labels = "one two three four five six seven eight nine".split()
4 class TestFrame(wx.Frame):
5 def __init__(self):
6 wx.Frame.__init__(self, None, -1, "StaticBoxSizer Test")
7 self.panel = wx.Panel(self)
8 # make three static boxes with windows positioned inside them
9 box1 = self.MakeStaticBoxSizer("Box 1", labels[0:3])
10 box2 = self.MakeStaticBoxSizer("Box 2", labels[3:6])
11 box3 = self.MakeStaticBoxSizer("Box 3", labels[6:9])
12 # We can also use a sizer to manage the placement of other
13 # sizers (and therefore the windows and sub-sizers that they
14 # manage as well.)
15 sizer = wx.BoxSizer(wx.HORIZONTAL)
16 sizer.Add(box1, 0, wx.ALL, 10)
17 sizer.Add(box2, 0, wx.ALL, 10)
18 sizer.Add(box3, 0, wx.ALL, 10)
19 self.panel.SetSizer(sizer)
20 sizer.Fit(self)
21 def MakeStaticBoxSizer(self, boxlabel, itemlabels):
22 # first the static box
23 box = wx.StaticBox(self.panel, -1, boxlabel)
24 # then the sizer
25 sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
26 # then add items to it like normal
27 for label in itemlabels:
28 bw = BlockWindow(self.panel, label=label)
29 sizer.Add(bw, 0, wx.ALL, 2)
30 return sizer
31 app = wx.PySimpleApp()
32 TestFrame().Show()
33 app.MainLoop()
static box sizer是类wx.StaticBoxSizer的实例,wx.StaticBoxSizer是wx.BoxSizer的子类。它的构造函数要求的参数是静态框和方向:
wx.StaticBoxSizer(box, oient)
在这个构造函数中,orient的意义与原wx.BoxSizer相同,box参数是一个wx.StaticBox。对于static box sizer所定义的别的方法只有一个:GetStaticBox(),它返回用于建造该sizer的wx.StaticBox。一旦该sizer被创建,那么你就不能再改变这个静态框了。
wx.StaticBox类有一个用于wxPython控件的标准的构造函数,但是其中许多参数都有默认值,可以忽略。
wx.StaticBox(parent, id, label, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0, name="staticBox")
使用一个static box sizer,你不需要去设置pos, size, style, 或name参数,因为位置和尺寸将由sizer管理,并且没用单独用于wx.StaticBox的样式。这使得构造更简单:
box = wx.StaticBox(self.panel, -1, boxlabel)
到目前为止,我们已经展示了各种sizer,我们将给你展示如何在实际的布局中使用它们。对于另外一个用于创建一个复杂布局的例子,请参看第六章。
一个现实中使用sizer的例子
迄今为止,我们所展示的有关sizer的例子都是在显示它们的功能方面。下面,我们将展示一个如何使用sizer来建造一个真实的布局。图11.16显示了一个使用不同sizer建造的复杂程度适中的布局。
图11.16
例11.11显示了产生上图的代码。这段代码看起来有点复杂,但是我们将对它分块解读。
例11.11 用sizer来建造地址表单
#coding=utf-8 #!python import wx class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "Real World Test") panel = wx.Panel(self) # First create the controls topLbl = wx.StaticText(panel, -1, "Account Information")#1 创建窗口部件 topLbl.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) nameLbl = wx.StaticText(panel, -1, "Name:") name = wx.TextCtrl(panel, -1, ""); addrLbl = wx.StaticText(panel, -1, "Address:") addr1 = wx.TextCtrl(panel, -1, ""); addr2 = wx.TextCtrl(panel, -1, ""); cstLbl = wx.StaticText(panel, -1, "City, State, Zip:") city = wx.TextCtrl(panel, -1, "", size=(150,-1)); state = wx.TextCtrl(panel, -1, "", size=(50,-1)); zip = wx.TextCtrl(panel, -1, "", size=(70,-1)); phoneLbl = wx.StaticText(panel, -1, "Phone:") phone = wx.TextCtrl(panel, -1, ""); emailLbl = wx.StaticText(panel, -1, "Email:") email = wx.TextCtrl(panel, -1, ""); saveBtn = wx.Button(panel, -1, "Save") cancelBtn = wx.Button(panel, -1, "Cancel") # Now do the layout. # mainSizer is the top-level one that manages everything #2 垂直的sizer mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(topLbl, 0, wx.ALL, 5) mainSizer.Add(wx.StaticLine(panel), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) # addrSizer is a grid that holds all of the address info #3 地址列 addrSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5) addrSizer.AddGrowableCol(1) addrSizer.Add(nameLbl, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) addrSizer.Add(name, 0, wx.EXPAND) addrSizer.Add(addrLbl, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) addrSizer.Add(addr1, 0, wx.EXPAND) #4 带有空白空间的行 addrSizer.Add((10,10)) # some empty space addrSizer.Add(addr2, 0, wx.EXPAND) addrSizer.Add(cstLbl, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) # the city, state, zip fields are in a sub-sizer #5 水平嵌套 cstSizer = wx.BoxSizer(wx.HORIZONTAL) cstSizer.Add(city, 1) cstSizer.Add(state, 0, wx.LEFT|wx.RIGHT, 5) cstSizer.Add(zip) addrSizer.Add(cstSizer, 0, wx.EXPAND) #6 电话和电子邮箱 addrSizer.Add(phoneLbl, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) addrSizer.Add(phone, 0, wx.EXPAND) addrSizer.Add(emailLbl, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) addrSizer.Add(email, 0, wx.EXPAND) # now add the addrSizer to the mainSizer #7 添加Flex sizer mainSizer.Add(addrSizer, 0, wx.EXPAND|wx.ALL, 10) # The buttons sizer will put them in a row with resizeable # gaps between and on either side of the buttons #8 按钮行 btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.Add((20,20), 1) btnSizer.Add(saveBtn) btnSizer.Add((20,20), 1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20), 1) mainSizer.Add(btnSizer, 0, wx.EXPAND|wx.BOTTOM, 10) panel.SetSizer(mainSizer) # Fit the frame to the needs of the sizer. The frame will # automatically resize the panel as needed. Also prevent the # frame from getting smaller than this size. mainSizer.Fit(self) mainSizer.SetSizeHints(self) app = wx.PySimpleApp() TestFrame().Show() app.MainLoop()
#1 代码的第一部分是创建使用在窗口中的窗口部件,它们在这行开始。我们在增加sizer前将它们全部创建。
#2 在这个布局中的主sizer是mainSizer,它是一个竖直的box sizer。被添加到mainSizer的第一个元素是顶部的静态文本标签和一个static line。
#3 在box sizer中接下来的元素是addrSizer,它是一个flex grid sizer,它有两列,它两列用于容纳其余的地址信息。addrSizer的左列被设计用于静态文本标签,而右列用于得到文本控件。这意味着标签和控件需要被交替的添加,以保证grid的正确。你可以看到nameLbl, name, addrLbl, 和addr1是首先被添加到该flex grid中的四个元素。
#4 这接下来的行是不同的,因为这第二个地址行没有标签,一个(10,10)尺寸的空白块被添加,然后是addr2控件。
#5 接下来的行又有所不同,包括“City, State, Zip”的行要求三个不同的文本控件,基于这种情况,我们创建了一个水平的box sizer:cstSizer。这三个控件被添加给cstSizer,然后这个box sizer被添加到addrSizer。
#6 电话和电子邮箱行被添加到flex sizer。
#7 有关地址的flex sizer被正式添加到主sizer。
#8 按钮行作为水平box sizer被添加,其中有一些空白元素以分隔按钮。
在调整了sizer(mainSizer.Fit(self))和防止框架变得更小之后(mainSizer.SetSizeHints(self)),元素的布局就结束了。
在读这接下来的段落或运行这个例子之前,请试着想出该框架将会如何在水平和竖直方向上响应增长。
如果该窗口在竖直方向上的大小改变了,其中的元素不会移动。这是因为主sizer是一个垂直的box sizer,你是在它的主方向上改变尺寸,它没有一个顶级元素是以大于0的比列被添加的。如果这个窗口在水平方向被调整尺寸,由于这个主sizer是一个垂直的box sizer,你是在它的次方向改变尺寸,因此它的所有有wx.EXPAND标记的元素将水平的伸展。这意味着顶部的标签不增长,但是static line和子sizer将水平的增长。用于地址的flex grid sizer指定列1是可增长的,这意味着包含文本控件的第二列将增长。在“City, State, Zip” 行内,比列为1的city元素将伸展,而state和ZIP控件将保持尺寸不变。按钮将保持原有的尺寸,因为它们的比列是0,但是按钮所在行的空白空间将等分地占据剩下的空间,因为它们每个的比列都是1。
因此如果你想出的窗口伸展的样子和下图11.17相同,那么你是正确的。
图11.17
本章小结
1、Sizer是对wxPython程序中管理布局问题的解决方法。不用手动指定每个元素在布局中的位置和尺寸,你可以将元素添加到一个sizer中,由sizer负责将每个元素放置到屏幕上。当用户调整框架的尺寸时,sizer管理布局是相当好的。
2、所有的wxPython的sizer都是类wx.Sizer的一个子类的实例。要使用一个sizer,你需要把它与一个容器型的窗口部件关联起来。然后,对于要添加到容器中的子窗口部件,你也必需将它们添加到该sizer。最后,调用该sizer的Fit()方法来触发该sizer的算法,以便布局和放置。
3、所有的sizer开始都给它们的孩子以最小的尺寸。每种sizer各自使用不同的机制来放置窗口部件,所以相同组的窗口部件放置在不同的sizer中时,它们的外观也是不同的。
4、或许在wxPython中,最简单的sizer是grid sizer(wx.GridSizer)。在grid sizer中,元素按照它们被添加给sizer的顺序被放置在一个二维的网格中,按照从左到右,从上到下的方式排列。通常你负责设定列数,sizer确定行数。你能够同时指定行和列,如果你愿意的话。
5、所有的sizer都有各自不同的用来将窗口部件添加到sizer的方法。由于窗口部件添加到sizer中的顺序对于最后的布局是重要的,所以有不同的方法用来添加一个新的窗口部件到列表中的前面、后面或任意点。在一个窗口部件被添加到sizer时,另外的属性可以被设置,它们控制当sizer增减时,其中的子元素如何变化。sizer也能够被配置来在对象的某些或全部边上放置一个边界间隙。