ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 第十一章 使用sizer放置构件 1. [使用sizer放置窗口部件](#A.2BT391KA-sizer.2BZT5.2FbnqXU.2BOQ6E72-) 1. [sizer是什么?](#sizer.2BZi9OwE5I.2Fx8-) 2. [基本的sizer:grid](#A.2BV.2FpnLHaE-sizer.2B.2Fxo-grid) 1. [什么是grid](#A.2BTsBOSGYv-grid) 2. [如何对sizer添加或移除孩子?](#A.2BWYJPVVv5-sizer.2BbftSoGIWefuWZFtpW1D.2FHw-) 3. [sizer是如何管理它的孩子的尺寸和对齐的?](#sizer.2BZi9Zgk9Ve6F0BluDdoRbaVtQdoRcOlv4VIxb.2BZ9QdoT.2FHw-) 4. [能够为sizer或它的孩子指定一个最小的尺寸吗?](#A.2BgP1ZH046-sizer.2BYhZbg3aEW2lbUGMHW5pOAE4qZwBcD3aEXDpb.2BFQX.2Fx8-) 5. [sizer如何管理每个孩子的边框?](#sizer.2BWYJPVXuhdAZrz04qW2lbUHaEj7loRv8f-) 3. [使用其它类型的sizer](#A.2BT391KFF2W4N8e1eLdoQ-sizer) 1. [什么是flex grid sizer?](#A.2BTsBOSGYv-flex_grid__sizer.2B.2Fx8-) 2. [什么是grid bag sizer?](#A.2BTsBOSGYv-grid_bag__sizer.3F) 3. [什么是box](#A.2BTsBOSGYv-box) 4. [什么是static](#A.2BTsBOSGYv-static) 4. [一个现实中使用sizer的例子](#A.2BTgBOKnOwW55OLU9.2FdSg-sizer.2BdoRPi1tQ-) 5. [本章小结](#A.2BZyx64FwPftM-) **本章内容** ·理解`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** ![](https://box.kancloud.cn/2016-08-21_57b99644c3cf1.gif) 当你调整`grid` `sizer`的大小时,每个部件之间的间隙将随之改变,但是默认情况下,窗口部件的尺寸不会变,并且始终按左上角依次排列。图11.2显示了调整尺寸后的同一窗口。 **图11.2** ![](https://box.kancloud.cn/2016-08-21_57b99644d64b9.gif) 例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** ![](https://box.kancloud.cn/2016-08-21_57b99644eb6c3.gif) 如果`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** ![](https://box.kancloud.cn/2016-08-21_57b996450aa2b.gif) 例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** ![](https://box.kancloud.cn/2016-08-21_57b996451d356.gif) 例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** ![](https://box.kancloud.cn/2016-08-21_57b996453eb19.gif) 例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** ![](https://box.kancloud.cn/2016-08-21_57b996454fdd6.gif) 与图11.5相比较,对于相同的布局,图11.5中每个单元格的尺寸与中间对象的相同,在`flex` `grid` `sizer`中,单元格的尺寸大小根据它所在的行和列来定。它们宽度是该列中宽度最大的项目的宽度,它们的高度是该行中宽度最高的项目的宽度。在这里,项目“`four`”和项目“`six`”的单元格的高度比项目本身的高度更高,因为其同行中的项目“`five`”,而"`two`"和"`seven`"的单元格的宽度也更宽。“`one`,” “`three`,” “`seven`,” 和“`nine`”的单元格是正常的尺寸,并且不受较大的窗口部件的影响。 图11.8展示了当调整窗口尺寸时,`flex` `grid` `sizer`的默认行为——单元格的尺寸不改变。 **图11.8** ![](https://box.kancloud.cn/2016-08-21_57b99645626af.gif) 例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** ![](https://box.kancloud.cn/2016-08-21_57b99645626af.gif) 正如你可以看到的,当所有的单元格增大时,中间的行和列的增大是两端的两倍。窗口部件的没有改变尺寸以填表充它们的单元格,虽然可以通过在当它们被添加到`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** ![](https://box.kancloud.cn/2016-08-21_57b996457b9c6.gif) 图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** ![](https://box.kancloud.cn/2016-08-21_57b996458e9b5.gif) **图11.12** ![](https://box.kancloud.cn/2016-08-21_57b99645a4c4f.gif) 图11.13显示了一个垂直的`sizer`,其中一个窗口部件被设置成可扩展并填充有效的垂直空间。图11.14展示了一个垂直的`sizer`,其中的两个窗口部件设置为按不同的比例占据有效的垂直空间。 **图11.13** ![](https://box.kancloud.cn/2016-08-21_57b99645b8d83.gif) **图11.14** ![](https://box.kancloud.cn/2016-08-21_57b99645ce5f2.gif) 生成以上四个`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** ![](https://box.kancloud.cn/2016-08-21_57b99645e12cd.gif) 例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** ![](https://box.kancloud.cn/2016-08-21_57b99646012bd.gif) 例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** ![](https://box.kancloud.cn/2016-08-21_57b9964613709.gif) ## 本章小结 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`也能够被配置来在对象的某些或全部边上放置一个边界间隙。