第十一章 使用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 块状窗口,在后面的例子中用作一个窗口部件
import wx
class BlockWindow(wx.Panel):
def __init__(self, parent, ID=-1, label="",
pos=wx.DefaultPosition, size=(100, 25)):
wx.Panel.__init__(self, parent, ID, pos, size,
wx.RAISED_BORDER, label)
self.label = label
self.SetBackgroundColour("white")
self.SetMinSize(size)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, evt):
sz = self.GetClientSize()
dc = wx.PaintDC(self)
w,h = dc.GetTextExtent(self.label)
dc.SetFont(self.GetFont())
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
import wx
from blockwindow import BlockWindow
labels = "one two three four five six seven eight nine".split()
class GridSizerFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Basic Grid Sizer")
sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)#创建grid sizer
for label in labels:
bw = BlockWindow(self, label=label)
sizer.Add(bw, 0, 0)#添加窗口部件到sizer
self.SetSizer(sizer)#把sizer与框架关联起来
self.Fit()
app = wx.PySimpleApp()
GridSizerFrame().Show()
app.MainLoop()
你可以从例11.2看到,一个grid
sizer
是类wx.GridSizer
的一个实例。构造函数显式地设置四个属性,这些属性是grid
sizer
独一无二的:
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()
方法有三个不同的样式:
Add(window, proportion=0, flag=0, border=0, userData=None)
Add(sizer, proportion=0, flag=0, border=0, userData=None)
Add(size, proportion=0, flag=0, border=0, userData=None)
第一个版本是你最常要用到的,它使你能够将一个窗口部件添加到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()
方法使你能够按任意的索引来放置新的窗口部件。它也有三个形式:
Insert(index, window, proportion=0, flag=0, border=0, userData=None)
Insert(index, sizer, proportion=0, flag=0, border=0, userData=None)
Insert(index, size, proportion=0, flag=0, border=0, userData=None)
使用Prepend()
方法
该方法将新的窗口部件、sizer
或空白添加到sizer
的列表的开头,这意味所添加的东西将被显示到左上角:
Prepend(window, proportion=0, flag=0, border=0, userData=None)
Prepend(sizer, proportion=0, flag=0, border=0, userData=None)
Prepend(size, proportion=0, flag=0, border=0, userData=None)
图11.3显示了在例11.1中如果Add()
替换为Prepend()
后的布局。
图11.3
如果sizer
已在屏幕上显示了,而你又要给sizer
添加一个新的项目,那么你需要调用sizer
的Layout()
方法来迫使sizer
自己重新排列,以容纳新的项。
使用Detach()
方法
为了从sizer
中移除一项,你需要调用Detach()
方法,它从sizer
中移除项目,但是没有销毁该项目。这对于你以后再使用它是有用的。使用Detach()
有三种方法。你可以将你想要移除的窗口、sizer
对象、对象的索引作为参数传递给Detach()
:
Detach(window)
Detach(sizer)
Detach(index)
在这三种情况中,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
import wx
from blockwindow import BlockWindow
labels = "one two three four five six seven eight nine".split()
#对齐标记
flags = {"one": wx.ALIGN_BOTTOM, "two": wx.ALIGN_CENTER,
"four": wx.ALIGN_RIGHT, "six": wx.EXPAND, "seven": wx.EXPAND,
"eight": wx.SHAPED}
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "GridSizer Resizing")
sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
for label in labels:
bw = BlockWindow(self, label=label)
flag = flags.get(label, 0)
sizer.Add(bw, 0, flag)
self.SetSizer(sizer)
self.Fit()
app = wx.PySimpleApp()
TestFrame().Show()
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
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, "GridSizer Test")
sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
for label in labels:
bw = BlockWindow(self, label=label)
sizer.Add(bw, 0, 0)
center = self.FindWindowByName("five")
center.SetMinSize((150,50))
self.SetSizer(sizer)
self.Fit()
app = wx.PySimpleApp()
TestFrame().Show()
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()
方法。它也有三个形式:
SetItemMinSize(window, size)
SetItemMinSize(sizer, size)
SetItemMinSize(index, size)
这里,参数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
代码
import wx
from blockwindow import BlockWindow
labels = "one two three four five six seven eight nine".split()
#边框标记
flags = {"one": wx.BOTTOM, "two": wx.ALL, "three": wx.TOP,
"four": wx.LEFT, "five": wx.ALL, "six": wx.RIGHT,
"seven": wx.BOTTOM | wx.TOP, "eight": wx.ALL,
"nine": wx.LEFT | wx.RIGHT}
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "GridSizer Borders")
sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)
for label in labels:
bw = BlockWindow(self, label=label)
flag = flags.get(label, 0)
sizer.Add(bw, 0, flag, 10)#添加指定边框的窗口部件
self.SetSizer(sizer)
self.Fit()
app = wx.PySimpleApp()
TestFrame().Show()
app.MainLoop()
要在一个sizer
中的窗口部件周围放置边框,需要两步。第一步是当窗口部件被添加到该sizer
时,传递额外的标记给flags
参数。你可以使用标记wx.ALL
来指定边框围绕整个窗口部件,或使用wx.BOTTOM
, wx.LEFT
, wx.RIGHT
, wx.TOP
来指定某一边有边框。这些标记当然可以组合成你想要的,如wx.RIGHT
| wx.BOTTOM
将使你的窗口部件的右边和底边有边框。由于边框、尺寸调整、对齐这些信息都是经由flags
参数,所以对于同一个窗口部件,你通常必须将三种标记组合起来使用。
在你传递了边框信息到flags
参数后,你也需要传递边框宽度的像素值给border
参数。例如,下面的调用将添加窗口部件到sizer
列表的尾部,并使该窗口部件的周围有5个像素宽度的边框:
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
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, "FlexGridSizer")
sizer = wx.FlexGridSizer(rows=3, cols=3, hgap=5, vgap=5)
for label in labels:
bw = BlockWindow(self, label=label)
sizer.Add(bw, 0, 0)
center = self.FindWindowByName("five")
center.SetMinSize((150,50))
self.SetSizer(sizer)
self.Fit()
app = wx.PySimpleApp()
TestFrame().Show()
app.MainLoop()
一个flex
grid
sizer
是wx.FlexGridSizer
的一个实例。类wx.FlexGridSizer
是wx.GridSizer
的子类,所以wx.GridSizer
的属性方法依然有效。wx.FlexGridSizer
的构造函数与其父类的相同:
wx.FlexGridSizer(rows
, cols
, vgap
, hgap)
为了当sizer
扩展时,使一行或列也扩展,你需要使用适当的方法显式地告诉该sizer
该行或列是可扩展的:
AddGrowableCol(idx, proportion=0)
AddGrowableRow(idx, proportion=0)
当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
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, "Resizing Flex Grid Sizer")
sizer = wx.FlexGridSizer(rows=3, cols=3, hgap=5, vgap=5)
for label in labels:
bw = BlockWindow(self, label=label)
sizer.Add(bw, 0, 0)
center = self.FindWindowByName("five")
center.SetMinSize((150,50))
sizer.AddGrowableCol(0, 1)
sizer.AddGrowableCol(1, 2)
sizer.AddGrowableCol(2, 1)
sizer.AddGrowableRow(0, 1)
sizer.AddGrowableRow(1, 5)
sizer.AddGrowableRow(2, 1)
self.SetSizer(sizer)
self.Fit()
app = wx.PySimpleApp()
TestFrame().Show()
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
的构造函数与它的父类有点不同:
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
import wx
from blockwindow import BlockWindow
labels = "one two three four".split()
class TestFrame(wx.Frame):
title = "none"
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
sizer = self.CreateSizerAndWindows()
self.SetSizer(sizer)
self.Fit()
class VBoxSizerFrame(TestFrame):
title = "Vertical BoxSizer"
def CreateSizerAndWindows(self):
sizer = wx.BoxSizer(wx.VERTICAL)
for label in labels:
bw = BlockWindow(self, label=label, size=(200,30))
sizer.Add(bw, flag=wx.EXPAND)
return sizer
class HBoxSizerFrame(TestFrame):
title = "Horizontal BoxSizer"
def CreateSizerAndWindows(self):
sizer = wx.BoxSizer(wx.HORIZONTAL)
for label in labels:
bw = BlockWindow(self, label=label, size=(75,30))
sizer.Add(bw, flag=wx.EXPAND)
return sizer
class VBoxSizerStretchableFrame(TestFrame):
title = "Stretchable BoxSizer"
def CreateSizerAndWindows(self):
sizer = wx.BoxSizer(wx.VERTICAL)
for label in labels:
bw = BlockWindow(self, label=label, size=(200,30))
sizer.Add(bw, flag=wx.EXPAND)
# Add an item that takes all the free space
bw = BlockWindow(self, label="gets all free space", size=(200,30))
sizer.Add(bw, 1, flag=wx.EXPAND)
return sizer
class VBoxSizerMultiProportionalFrame(TestFrame):
title = "Proportional BoxSizer"
def CreateSizerAndWindows(self):
sizer = wx.BoxSizer(wx.VERTICAL)
for label in labels:
bw = BlockWindow(self, label=label, size=(200,30))
sizer.Add(bw, flag=wx.EXPAND)
# Add an item that takes one share of the free space
bw = BlockWindow(self,
label="gets 1/3 of the free space",
size=(200,30))
sizer.Add(bw, 1, flag=wx.EXPAND)
# Add an item that takes 2 shares of the free space
bw = BlockWindow(self,
label="gets 2/3 of the free space",
size=(200,30))
sizer.Add(bw, 2, flag=wx.EXPAND)
return sizer
app = wx.PySimpleApp()
frameList = [VBoxSizerFrame, HBoxSizerFrame,
VBoxSizerStretchableFrame,
VBoxSizerMultiProportionalFrame]
for klass in frameList:
frame = klass()
frame.Show()
app.MainLoop()
box
sizer
是类wx.BoxSizer
的实例,wx.BoxSizer
是wx.Sizer
的子类,相对于wx.Sizer
,wx.BoxSizer
几乎没有增加新的方法。wx.BoxSizer
的构造函数有一个参数:
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
的一个例子
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, "StaticBoxSizer Test")
self.panel = wx.Panel(self)
# make three static boxes with windows positioned inside them
box1 = self.MakeStaticBoxSizer("Box 1", labels[0:3])
box2 = self.MakeStaticBoxSizer("Box 2", labels[3:6])
box3 = self.MakeStaticBoxSizer("Box 3", labels[6:9])
# We can also use a sizer to manage the placement of other
# sizers (and therefore the windows and sub-sizers that they
# manage as well.)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(box1, 0, wx.ALL, 10)
sizer.Add(box2, 0, wx.ALL, 10)
sizer.Add(box3, 0, wx.ALL, 10)
self.panel.SetSizer(sizer)
sizer.Fit(self)
def MakeStaticBoxSizer(self, boxlabel, itemlabels):
# first the static box
box = wx.StaticBox(self.panel, -1, boxlabel)
# then the sizer
sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
# then add items to it like normal
for label in itemlabels:
bw = BlockWindow(self.panel, label=label)
sizer.Add(bw, 0, wx.ALL, 2)
return sizer
app = wx.PySimpleApp()
TestFrame().Show()
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
也能够被配置来在对象的某些或全部边上放置一个边界间隙。