wxPython布局

wxPython布局

Jan 9, 2014
Coding
Python, WxPython

布局方式有两个,绝对布局和相对布局。绝对布局就是手动的设定各个窗体、控件的大小和位置,这有一个缺点是当主窗体的尺寸可变且变化时,各个控件的位置不会随之相应的改变。而使用相对布局则主窗体的尺寸变化时,各个控件会随之相应的变化,以适应新的窗口大小。

相对布局使用布局管理器Sizer实现,wxPython中的布局管理器有:BoxSizer(行、列),GridSizer(等大小的二维网格),FlexGridSizer(大小可不等的二维网格),GridBagSizer(可设定大小、跨行列的二维网格),StaticBoxSizer(在BoxSizer外加了标题和环线)。BoxSizer和GridSizer都继承自wx.Sizer类,FlexGridSizer继承自GridSizer,GridBagSizer继承自FlexGridSizer,StaticBoxSizer继承自BoxSizer。

wx.Sizer –> BoxSizer -> StaticBoxSizer

wx.Sizer –> GridSizer -> FlexGridSizer ->GridBagSizer

BoxSizer – 单框布局 #

按照一行(orientation=wx.HORIZONTAL)、或者一列(orientation=wx.VERTICAL)布局。子控件可以在高度和宽度两个方向变化。在主方向上(初始的orientation)每个控件可以按比例的伸缩,取决于子控件添加时指定的proportion参数。proportion=0,表示不伸缩。比如有三个控件proportion值分别是1,2,3,那么伸缩变化的空间会按照1:2:3的比例分配给它们,即分别占1/6,2/6,3/6。

示例

效果图如下

boxgrid

代码如下

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#author:JarvisChu
#blog:zhujiangtao.com

import wx

class ExampleFrame(wx.Frame):
    def __init__(self,parent=None,id=-1,title='MyFrame'):
        wx.Frame.__init__(self,parent=parent,id=id,title=title)
        self.Centre()

        btn1 = wx.Button(self,label='btn1')
        btn2 = wx.Button(self,label='btn2')
        btn3 = wx.Button(self,label='btn3')
        btn4 = wx.Button(self,label='btn4')
        btn5 = wx.Button(self,label='btn5')

        #BoxSizer布局
        vBoxSizer = wx.BoxSizer(wx.VERTICAL)
        vBoxSizer.Add(btn1, proportion=1,flag=wx.TOP|wx.BOTTOM|wx.EXPAND,border=5)
        vBoxSizer.Add(btn2, proportion=2,flag=wx.TOP|wx.BOTTOM|wx.EXPAND,border=10)
        vBoxSizer.Add(btn3, proportion=3,flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=10)
        vBoxSizer.Add(btn4, proportion=2,flag=wx.ALL|wx.EXPAND,border=10)
        vBoxSizer.Add(btn5, proportion=1,flag=wx.TOP|wx.BOTTOM|wx.EXPAND,border=5)
        #vBoxSizer.AddMany([(btn3,proportion=2,flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=10),(btn4)])
        self.SetSizer(vBoxSizer)

class ExampleApp(wx.App):
    def OnInit(self):
        frame = ExampleFrame()
        frame.Show()
        return True

if __name__ == "__main__":
    app = ExampleApp()
    app.MainLoop()

在ExampleFrame类的初始化函数中,新建了5个Button按钮,然后新建了垂直的BoxSizer布局管理器

``python vBoxSizer = wx.BoxSizer(wx.VERTICAL)


参数wx.VERTICAL表示垂直排列,即一列;如果为wx.HORIZONTAL,则表示为水平排列,即一行。

```python
vBoxSizer.Add(btn1, proportion=1,flag=wx.TOP|wx.BOTTOM|wx.EXPAND,border=5)

通过Add方法将控件加入到BoxSizer中,第一个参数为要加入的控件名;proportion是控件的比例;flag用于指示控件的边框和对齐方式,其中边框可以是wx.TOP、wx.BOTTOM、wx.LEFT、wx.RIGHT中的一个或是组合,或者是wx.ALL,表示控件的上、下、左、右是否有边框,对齐方式可以是wx.EXPAND、wx.ALIGN_CENTER、wx.ALIGN_LEFT、wx.ALIGN_RIGHT、wx.ALIGN_TOP 等等;border表示边框的宽度。

self.SetSizer(vBoxSizer)

调用ExampleFrame的SetSizer方法将vBoxSizer设置为它的布局管理器。

注:后面的关于布局管理器的代码都是使用的这个框架代码,都是在ExampleFrame内的构造函数__init__中实现的。为了简洁和突出重点,后面只给出布局管理相关代码,框架代码就不写了。

GridSizer – 网格布局 #

所有的子控件按照二维表格布局,每个方格大小都相同。表格的每个方格的宽度是添加到Sizer的最宽控件的宽度,方格的高度是最高控件的高度。每个控件间的水平和垂直距离可以通过可选参数 hgap 和 vgap 指定。控件按照加入GridSizer的顺序依次加入到表格中(以行序为主序)。即子控件先排放在第一行,然后第二行,直到所有行都排满。(如果需要,添加子控件时会添加新的行)

如果你需要对每个方格有更大的控制,那么使用wx.GridBagSizer.

示例

效果图如下

gridsizer

关键代码如下

btn1 = wx.Button(self, label='btn1')
btn2 = wx.Button(self, label='btn2')
btn3 = wx.Button(self, label='btn3')
btn4 = wx.Button(self, label='btn4')
btn5 = wx.Button(self, label='btn5')

#GridSizer布局
gridSizer = wx.GridSizer(2,3,10,10)#2行3列,水平和垂直距离都是10
gridSizer.Add(btn1,flag=wx.EXPAND)
gridSizer.AddMany([(btn2,0,wx.EXPAND),(btn3,0,wx.EXPAND),
(btn4,0,wx.EXPAND),(btn5,0,wx.EXPAND)])
self.SetSizer(gridSizer)

新建一个GridSizer布局管理器,指定为2行3列,其中的子控件水平和垂直距离都是10。GridSizer构造函数原型如下

wx.GridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)

vgap指定垂直距离,hgap为水平距离。

使用Add方法,可以添加一个子控件。还可以使用AddMany方法,一次性添加多个子控件,参数是一个所有子控件的列表,每个子控件又是一个元组,如(btn2,0,wx.EXPAND),btn2为控件名,0是proportion(在GridSizer中是无效的,因为所有子控件所占空间大小相同),wx.EXPAND是对齐方式。

FlexGridSizer – 可伸缩网格布局 #

FlexGridSizer将其所有子控件布局到一个二维的网格中,一行中的所有控件高度相等,一列中的所有控件宽度相等,但不是所有行的高度都需要相等,不是所有列的宽度都需要相等。

  • AddGrowableCol(idx, proportion=0) 设定第idx的列为可伸缩,且伸缩比例为proportion
  • AddGrowableRow(idx, proportion=0) 设定第idx的行为可伸缩,且伸缩比例为proportion

示例1

#FlexGridSizer布局
flexGridSizer = wx.FlexGridSizer(3,3,10,10)
flexGridSizer.AddMany([(btn1,0,wx.EXPAND),(btn2,0,wx.EXPAND),
(btn3,0,wx.EXPAND),(btn4,0,wx.EXPAND),
(btn5,0,wx.EXPAND),(btn6,0,wx.EXPAND),
(btn7,0,wx.EXPAND),(btn8,0,wx.EXPAND)])
self.SetSizer(flexGridSizer)

flexgridsizer1

示例1仅仅创建了一个FlexGridSizer布局,没有使用AddGrowableCol或AddGrowableRow设置可以伸缩的列或行。所以网格的每个方格大小都是相同且不随窗口尺寸变化而变化的

示例2

在示例1的 AddMany 函数后加入下面一行代码

flexGridSizer.AddGrowableCol(0,1)

设定第0列为可伸缩的,且初始时比例为1(当只有一列可伸缩时,这个比例没有什么作用)。效果如下

flexgridsizer2

窗口尺寸变化时,只有第0列跟随变化。其它的列保持不变。

示例3

设置可伸缩行和示例2设置可伸缩列的大致相同。使用AddGrowableRow函数。在示例1代码AddMany函数后加入下面2行代码。使得第0行和第1行都可以伸缩。

flexGridSizer.AddGrowableRow(0,1)
flexGridSizer.AddGrowableRow(1,2)

flexgridsizer3

GridBagSizer – 自由网格布局 #

继承自wx.FlexGridSizer布局,可将它的所有子控件像wx.FlexGridSizer一样,布局到一个二维网格中。GridBagSizer可用显示的设置子控件的在网格中的位置(使用wx.GBPosition,或者添加子控件时使用元组(row,col)指定),子控件可以跨行和跨列(使用wx.GBSpan,或者添加子控件时使用元组(rowspan,colspan)指定)。网格的整体大小由最大的行数和列数决定。

GridBagSizer是比较常用的布局管理器,也是功能最强大的网格布局管理器。

示例1

#GridBagSizer
gridBagSizer = wx.GridBagSizer(vgap=10, hgap=10)
gridBagSizer.Add(btn1,pos=(0,0),span=wx.DefaultSpan,flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=5)
gridBagSizer.Add(btn2,pos=(0,1),span=(2,2),flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=5)
gridBagSizer.Add(btn3,pos=(0,3),span=wx.DefaultSpan,flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=5)
gridBagSizer.Add(btn4,pos=(1,3),span=wx.DefaultSpan,flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=5)
self.SetSizer(gridBagSizer)

gridbagsizer

GridBagSizer的构造函数只有两个参数vgap和hgap,继承自GridSizer,用来指定子控件间的垂直和水平距离。GridBagSizer不用指定网格的大小,网格的大小是根据Add函数添加子控件时指定的子控件位置而定的。Add函数中,flag和border的也与GridSizer中相同(和BoxSizer也相同)。不同的是,多了pos和span两个参数。

  • pos = (row,col),用来指定添加的子控件摆放的网格的位置为第row行col列(下标从0开始算)
  • span = (rowspan,colspan),用来指定子控件跨越rowspan行,colspan列。

示例2

示例1中的控件大小是不随窗口大小改变而改变的。因为没有使用AddGrowableCol和AddGrowableRow指定可伸缩的列和行(这两个函数继承自FlexGridSizer)。在示例1代码最后一行之前加入下面代码,使得每行每列都可伸缩。

gridBagSizer.AddGrowableCol(0,1) #第0列
gridBagSizer.AddGrowableCol(1,1) #第1列
gridBagSizer.AddGrowableCol(2,1) #第2列
gridBagSizer.AddGrowableCol(3,1) #第3列
gridBagSizer.AddGrowableRow(0,1) #第0行
gridBagSizer.AddGrowableRow(1,1) #第1行

gridbagsizer1

StaticBoxSizer – 静态边框布局 #

继承在BoxSizer,与BoxSizer不同的是在所有子控件的外面加了一个wx.StaticBox,也就是在子控件外围增加一个边框,并在边框上增加一个静态文本。

注意:StaticBox必须分开创建,然后传递给StaticBoxSizer的构造函数。

示例

staticboxsizer

关键代码如下

btnOK = wx.Button(self, label='Submit')
#StaticBoxSizer
staticBox = wx.StaticBox(self, -1, "StaticBoxSizer Example")
staticBoxSizer = wx.StaticBoxSizer(staticBox, wx.VERTICAL)

st = wx.StaticText(self,label="Please input your name")
tc = wx.TextCtrl(self,style=wx.TE_MULTILINE | wx.TE_RICH2)

staticBoxSizer.Add(st, 1, wx.TOP|wx.EXPAND)
staticBoxSizer.Add(tc, 2, wx.TOP|wx.EXPAND)
staticBoxSizer.Add(btnOK, 4, wx.TOP|wx.EXPAND)
self.SetSizer(staticBoxSizer)

创建了一个Button按钮btnOK,一个StaticText静态文字框,一个TextCtrl文本框,用来添加到布局管理器。

创建StaticBoxSizer控件时,需要传入一个事先定义的StaticBox静态框。

官方文档及翻译 #

wx.Sizer #

wx.Sizer is the abstract base class used for laying out subwindows in a window. You cannot use wx.Sizer directly; instead, you will have to use one of the sizer classes derived from it such as wx.BoxSizer, wx.StaticBoxSizer, wx.GridSizer, wx.FlexGridSizer and wx.GridBagSizer.

wxSizer是在窗口中对子窗口进行布局的虚基类。你不能直接使用wx.Sizer,而应该使用从它派生出的wx.BoxSizer, wx.StaticBoxSizer, wx.GridSizer, wx.FlexGridSizer 和 wx.GridBagSizer.

The concept implemented by sizers in wxWidgets is closely related to layout tools in other GUI toolkits, such as Java’s AWT, the GTK toolkit or the Qt toolkit. It is based upon the idea of the individual subwindows reporting their minimal required size and their ability to get stretched if the size of the parent window has changed. This will most often mean that the programmer does not set the original size of a dialog in the beginning, rather the dialog will assigned a sizer and this sizer will be queried about the recommended size. The sizer in turn will query its children, which can be normal windows or contorls, empty space or other sizers, so that a hierarchy of sizers can be constructed. Note that wxSizer does not derive from wxWindow and thus do not interfere with tab ordering and requires very little resources compared to a real window on screen.

wxPython(wxWidget)中的实现的布局和其它GUI 工具库(如Java AWT,GTK,QT)中的布局相近。它基于这样的思想:独立的子窗口声明它们需要的最小尺寸,以及当父窗口尺寸变化时它们能够相应的伸缩。这通常意味着程序员开始时不需要设定对话框的大小,而是分配给对话框一个sizer,这个sizer会查询对话框推荐的大小。这个sizer反过来也会查询它的子控件,子控件可以是正常的窗口、控件、空白空间或者其它的sizer,这样就建立起来了一个sizer的继承图表。注意,wx.Sizer不是从wx.Window派生的,所以它不影响tab ordering,而且相比于屏幕上的一个真实的窗体,它需要的资源很少。

What makes sizers so well fitted for use in wxWidgets is the fact that every control reports its own minimal size and the algorithm can handle differences in font sizes or different window (dialog item) sizes on different platforms without problems. If for example the standard font as well as the overall design of Mac widgets requires more space than on Windows, then the initial size of a dialog using a sizer will automatically be bigger on Mac than on Windows.

sizer之所以能够很好的适应它所在的wxWidget,是因为每一个控件都会声明它自己需要的最小尺寸,算法可以处理不同平台上不同的字体大小或者窗口(或者窗口子控件)大小。比如,Mac系统的控件设计和标准字体比Windows上需要更多的空白,那么使用sizer的对话框的初始大小在Mac系统上会比Windows自动的更大一些。

Sizers may also be used to control the layout of custom drawn items on the window. The Add, Insert, and Prepend functions return a pointer to the newly addedwx.SizerItem. Just add empty space of the desired size and attributes, and then use the wx.SizerItem.GetRect method to determine where the drawing operations should take place.

Sizer还可以用来控制窗口上用户自绘制项目(custom draw items)的布局。 Add、 Insert 和 Prepend 函数返回一个指向最新添加的 wx.SizerItem 的指针。只需要添加一个需要的尺寸的空白空间和属性,然后使用wx.SizerItem.GetRect方法决定绘制操作要应用的地方。

BoxSizer #

The basic idea behind a box sizer is that windows will most often be laid out in rather simple basic geometry, typically in a row or a column or nested hierarchies of either. A wx.BoxSizer will lay out its items in a simple row or column, depending on the orientation parameter passed to the constructor.

BoxSizer布局是基于这样的思想:窗口布局通常都很简单,典型的比如一行、或者一列、或者行列的嵌套。wx.BoxSizer会将它的子控件布局成一行或者一列,取决于构造函数传入的orientation参数是wx.HORIZONTAL还是wx.VERTICAL。

vbox = wx.BoxSizer(wx.VERTICAL) #一列
hbox = wx.BoxSizer(wx.HORIZONTAL) #一行

It is the unique feature of a box sizer, that it can grow in both directions (height and width) but can distribute its growth in the main direction (horizontal for a row) unevenly among its children. This is determined by the proportion parameter give to items when they are added to the sizer. It is interpreted as a weight factor, i.e. it can be zero, indicating that the window may not be resized at all, or above zero. If several windows have a value above zero, the value is interpreted relative to the sum of all weight factors of the sizer, so when adding two windows with a value of 1, they will both get resized equally and each will receive half of the available space after the fixed size items have been sized. If the items have unequal proportion settings then they will receive a coresondingly unequal allotment of the free space.

BoxSizer布局的一个唯一的特点是,它可以在两个方向上(高和宽)上延伸,但是可以在它主方向(初始设定的orientation)上的延伸不均匀的分布到它的子控件上。这个由每个控件在添加到Sizer时的指定的proportion(比例)参数决定。它可以被解释成一个权重,比如可以是0(表示控件的尺寸保持不变)或者大于0的数。如果有多个控件的proportion值大于0,这个值就会被解释成与Sizer所有子控件的proportion值的和有关,所以当添加两个子控件的proportion值都是1,那么它们会同比例的变化,每一个都获得伸缩后变化的空间的一半。如果每个子控件的proportion不相等,那么它们就能按比例的获得伸缩后变化的空间。

GridSizer #

A grid sizer is a sizer which lays out its children in a two-dimensional table with all cells having the same size. In other words, the width of each cell within the grid is the width of the widest item added to the sizer and the height of each grid cell is the height of the tallest item. An optional vertical and/or horizontal gap between items can also be specified (in pixels.)

GridSizer将它所有的子控件按照二维网格布局,每个方格大小都相同。换句话说,表格的每个方格的宽度是添加到Sizer的最宽控件的宽度,方格的高度是最高控件的高度。每个控件间的水平和垂直距离可以通过可选参数hgap和vgap指定。

Items are placed in the cells of the grid in the order they are added, in row-major order. In other words, the first row is filled first, then the second, and so on until all items have been added. (If neccessary, additional rows will be added as items are added.) If you need to have greater control over the cells that items are placed in then use the wx.GridBagSizer

控件按照加入GridSizer的顺序依次加入到网格中(以行序为主序)。换句话说,第一行首先被填充,然后是第二行,直到所有行都填满。(如果需要,添加子控件时会添加新的行)如果你需要对每个方格有更大的控制,那么使用wx.GridBagSizer.

FlexGridSizer #

A flex grid sizer is a sizer which lays out its children in a two-dimensional table with all table cells in one row having the same height and all cells in one column having the same width, but all rows or all columns are not necessarily the same height or width as in the wx.GridSizer.

FlexGridSizer将其所有子控件布局到一个二维的网格中,一行中的所有控件高度相等,一列中的所有控件宽度相等,但不是所有行的高度都需要相等,所有列的宽度都需要相等。

wx.FlexGridSizer can also size items equally in one direction but unequally (“flexibly”) in the other. If the sizer is only flexible in one direction (this can be changed using SetFlexibleDirection), it needs to be decided how the sizer should grow in the other (“non flexible”) direction in order to fill the available space. The SetNonFlexibleGrowMode method serves this purpose.

FlexGridSizer可以设定子控件在一个方向上大小相同,另一个方向上可以不等(这就是Flexible)。如果Sizer只在一个方向不等(可以使用SetFlexibleDirection设定),它需要指定Sizer在另一个方向上伸缩的方式以此来填充可用的空白空间,SetNonFlexibleGrowMode函数正是完成此功能的。

GridBagSizer #

A wx.Sizer that can lay out items in a virtual grid like a wx.FlexGridSizer but in this case explicit positioning of the items is allowed using wx.GBPosition, and items can optionally span more than one row and/or column using wx.GBSpan. The total size of the virtual grid is determined by the largest row and column that items are positioned at, adjusted for spanning.

一个wx.Sizer可以将它的所有子控件像wx.FlexGridSizer一样,布局到一个虚拟的网格中。但是这种情况下,可用使用wx.GBPosition显示的设置子控件的位置,使用wx.GBSpan,子控件可以跨越不止一行/列。虚拟网格的整体大小由最大的行和列决定。

StaticBoxSizer #

wx.StaticBoxSizer derives from and functions identically to the wx.BoxSizer and adds a wx.StaticBox around the items that the sizer manages. Note that this static box must be created separately and passed to the sizer constructor.

继承在BoxSizer,与BoxSizer不同的是在所有子控件的外面加了一个wx.StaticBox。注意:StaticBox必须分开创建,然后传递给StaticBoxSizer的构造函数。