wxpython - 布局和事件
这章主要记录布局器Sizer以及事件的用法。
// 目前还需要记录的:Sizer的Add方法加空白,Sizer的Layout,Sizer的Remove如何有效
■ 布局
之前介绍的所有组件,如果不把它们的pos写死的话,页面上它们会互相重叠,导致没法看。而Sizer就是一个很好的优化布局的工具,通过此可以灵活地管理组件之间的相对位置。
Sizer大概的可以被分成GridSizer(网格布局)和BoxSizer(线性布局).Sizer的用法概括起来就是创建Sizer之后将其关联到一个对象(通常是Panel)。然后用诸如Add,Insert等方法为Sizer添加组件。调用Fit等方法可以让Sizer自动计算子部件尺寸的大小。去除一个部件和Sizer的关联可以用Sizer.Detach方法。//这个存疑,尝试了各种各样的方法,包括了窗口Refresh等等,效果都不好
● GridSizer 网格布局
构造方法中有四个参数决定了这个布局地大致样式,分别是rows,cols,vgap,hgap分别表示网格行数,列数,两相邻列间相隔像素数,两相邻行间相隔像素数。
在用Add方法添加组件的时候默认是先填满一行再开始填下一行。简单GridSizer严格遵守网格布局的含义,当窗口大小发生变化时,其网格的大小也会相应变化,导致加在网格中的组件大小或间距也会发生相应的变化。GridSizer更加适合表格作业が,其改进版的FlexGridSizer可以更加灵活的设置大小和缩放的关系。
网格布局举例;
testSizer = GridSizer(rows=2,cols=2) totalPanel = Panel(self,-1) text1 = StaticText(totalPanel,-1,"Text1:") btn1 = Button(totalPanel,-1,"Button1") text2 = StaticText(totalPanel,-1,"Text2:") btn2 = Button(totalPanel,-1,"Button") testSizer.Add(text1,flag=ALIGN_CENTER) testSizer.Add(btn1,flag=ALIGN_CENTER) testSizer.Add(text2,flag=ALIGN_CENTER) testSizer.Add(btn2,flag=EXPAND) totalPanel.SetSizer(testSizer)
界面效果:
● FlexGridSizer
其构造方法和GridSizer是类似的。但它和GridSizer的区别在于,在默认情况下FGS的网格不会随着窗口的大小变化而变化。而所谓不默认的情况,就是用Sizer对象调用AddGrowableRow(x,m)/AddGrowableCol(y,n)。这两个方法的意思是使得某一行或某一列的网格会随着窗口大小而变化。比如AddGrowableRow(1)是说让第二行会随着窗口纵向长度变化而变化,可以添加第二个参数来决定这行变化的权重。比如AddGrowableRow(0,1);AddGrowableRow(1,2)的意思就是把第一行和第二行都设置为可纵向延伸,且窗口延伸一个单位,第一行延伸三分之一while第二行延伸三分之二个单位。同理AddGrowableCol就是控制列在横向方向的延伸和权重的。(权重这个概念在后面的BoxSizer中还会再提到)
注意:这里所说的可延伸都是只网格的可延伸,而具体到这个网格里面的组件可不可延伸扩展,,就要看Sizer在Add时的flag了。比如说flag设置为ALIGN_CENTER的话,网格延伸了但是组件始终保持在网格的正中间。EXPAND的话组件始终填满整个网格等等。
反过来说,在Sizer.Add组件时如果设置了flag=EXPAND但是这个组件所在的行列都没有被设置成Growable的话就是白瞎,窗口变动无法引起网格变动自然也无法引起组件的变动了。
● GridBagSizer
GS和FGS在添加时都是默认先填满第一行,填满第一行之后再转到第二行。而GBS可以指定在哪行哪列的网格添加组件。
GBS的构造方法没有rows和cols参数,最终Sizer会以几行几列的方式呈现取决于添加的靠近右下角的那个组件有多右下角= =
GBS在Add的时候必须指出两个参数,pos=(x,y)以及size=(m,n)意思是组件填充在第x+1行和第y+1列的网格以及该组件将占据m行n列的位置(有点像xlrd,xlwt里面的合并单元格属性)
● BoxSizer
BS是一个水平行or垂直列,具体是哪个通过构造方法的参数是wx.HORIZONTAL还是wx.VERTICAL来决定。对于HORIZONTAL,其可延伸方向就是纵向,对于VERTICAL自然就是横向了。
BS的Add方法中有proportion参数。proportion参数类似于上面提到的AddGrowableRow的第二个参数类似,就是表明了窗口在可延伸方向上占的延伸权重。通常可以把组件添加到几个HORIZONTAL的sizer里,再把这些sizer作为组件添加到一个VERTICAL的sizer里面去,形成一个看起来比较舒服的页面。注意,如果需要有窗口延伸时组件也延伸的效果的话就需要让那几个HORIZONTAL的sizer也要有flag=EXPAND,因为sizer和panel一样也是有大小的,如果不设置EXPAND,这些sizer的大小也是不会变的。
关于BS的Add,还有一点常用的,就是flag=LEFT|RIGHT|TOP|BOTTOM|ALL,border=xx 通过flag=选项中的一个或几个,并且配合border的数值,调整被Add的元素始终保持指定的几条边和周围的相距border数值个像素的位置。因为是常用的就不啰嗦了。
■ 事件
人和组件元素的一些互动会触发事件,事件在wx中以以下方式表示:
wx.EVT_BUTTON 按下按钮的事件
常用的事件还有:
EVT_SIZE 由于用户干预或由程序实现,当一个窗口大小发生改变时发送给窗口。
EVT_MOVE 由于用户干预或由程序实现,当一个窗口被移动时发送给窗口。
EVT_CLOSE 当一个框架被要求关闭时发送给框架。除非关闭是强制性的,否则可以调用event.Veto(true)来取消关闭。
EVT_PAINT 无论何时当窗口的一部分需要重绘时发送给窗口。
EVT_CHAR 当窗口拥有输入焦点时,每产生非修改性(Shift键等等)按键时发送。
EVT_IDLE 这个事件会当系统没有处理其它事件时定期的发送。
EVT_LEFT_DOWN 鼠标左键按下。
EVT_LEFT_UP 鼠标左键抬起。
EVT_LEFT_DCLICK 鼠标左键双击。
EVT_MOTION 鼠标在移动。
EVT_SCROLL 滚动条被操作。这个事件其实是一组事件的集合,如果需要可以被单独捕捉。
EVT_MENU 菜单被选中。
此外还可以自定义事件,不过我觉得暂时还用不到就不啰嗦细写了。可以参考http://blog.csdn.net/tingsking18/article/details/4033639
● Bind方法
仅仅有事件还是不够的,必须要把引起事件的组件和事件处理结合起来,这里用到的就是Bind函数
Bind隶属于wx.EvtHandler,是所有可显示对象(包括Frame在内的绝大多数组件)的父类,所以 这些组件都可以调用Bind。允许一个组件Bind多个事件,此时事件的处理函数中可以写event.Skip(True)来标识某个事件处理函数对改事件不做处理而直接跳过。
Bind的定义是Bind(绑定事件类型,事件处理器,事件源对象),但是通常可以通过某个组件来调用Bind比如btn.Bind(绑定事件类型,事件处理器)就好了。事件类型就是上面那一大票,而事件处理器传递的是个函数对象。这个函数对象要求有且只有一个没有默认值的参数,event。这样的话就引起了一个问题,当我想要传递一些额外的参数给事件处理函数时该怎么办?比如:
#这样子是运行正常的 btn.Bind(EVT_BUTTON,test) def test(event): print "Hello" #但这样会报参数个数不对的错 btn.Bind(EVT_BUTON,test) def test(event,name): print "Hello,%s"%(name) #而在Bind的时候又不能直接给test加参数。。
这种情况其实可以归类为一个更普遍一点的问题,就是如何为一个(在定义时没有给出参数默认值的)函数对象参数默认值?
解决方法:用lambda:
btn.Bind(EVT_BUTTON,lambda evt,name="Frank":test(evt,name)) def test(event,name): print "Hello,%s"%(name)
通过lambda为函数本身再做一层包装而返回一个包装后的函数对象(感觉有点像装饰器的意思)
■ 在整个wx中加入事件之后可以看到,组件之间的关系复杂起来了。建议的程序构造方式是这样子的:
每一个窗口都是一个类,不同窗口用不同类来表示
窗口中的组件写在类的构造方法里,并且把需要进行取值等和其他方法互动操作的组件要设置成“self.组件”。与组件绑定的事件处理方法写成“self.方法”的类方法。需要子窗口时把子窗口的构造方法中加个parent参数,然后在构造子窗口时把父窗口传递进去即可。