8.2.4 布局
布局指的是界面元素在界面中的位置安排。Tkinter 中提供了布局管理器,其任务是根据 程序员的要求以及其他一些约束来安排构件的位置。使用布局管理器的优点是程序员不需要 了解底层显示系统的细节,可以在较高层次上考虑界面布局问题。
如前所述,多数构件在创建之后还需进行布局才会显示在屏幕上,即要经过两个步骤:
w = Constructor(parent,...) w.GeometryManager(...)
其中 Constructor 是构件类(如 Label,Button 等),parent 是父构件,GeometryManager 是布局管理器方法。Tkinter 提供了三种布局管理器:Pack、Grid 和 Place。
Pack 布局管理器
Pack 布局管理器以紧凑的方式将构件在窗口中“打包”,调用构件的 pack 方法即可以这 种方式布局。具体的打包方式可以用一个比方来说明:设想窗口是由弹性材料制成的,当要 放入一个构件时,就先把窗口空间撑大到足够容纳该构件,然后将构件紧贴内部的某条边(缺 省是顶边)放入,然后重复此过程不断放入构件。可见,在缺省情形下,放入同一个窗口的 所有构件是沿垂直方向自顶向下一个紧贴一个进行布置的,但可以通过 pack 方法的 side 选项 设置成沿水平方向打包。
例如,执行下列语句后得到的布局效果如图 8.16(a)所示。
>>> Label(root,text="Input a number:").pack()
>>> Entry(root).pack()
>>> Button(root,text="OK").pack()
而执行下列语句后的布局效果如图 8.16(b)所示,这里用到了 side 属性:side=”left”使得构件 贴着左边放置。
>>> Label(root,text="Input a number:").pack(side="left")
>>> Entry(root).pack(side="left")
>>> Button(root,text="OK").pack(side="left")
图 8.16 Pack 布局管理器
如果对窗口中的所有基本构件以打包方式布局,将使大大小小的构件形成一摞,显然不 美观。实际应用中常见的做法是对框架进行打包布局,一个一个框架像集装箱一样并排或堆 叠,然后再将基本构件作为各框架的子构件进行布局,这样能使界面整齐美观。
虽然 Pack 管理器还提供其他布局选项,但在此不多介绍了,因为我们有更灵活、更好用 的布局管理器:Grid 和 Place。
pack 方法的“逆”方法是 pack_forget 方法,意为将用 pack 布局的构件从界面中拿掉, 从而构件变成不可见。注意,这时构件作为对象仍然存在,只是未显示在界面中而已,我们 随时可以再次调用任何布局管理器方法来使构件可见。
rid 布局管理器*
Grid 布局管理器将窗口或框架视为一个由行和列构成的二维表格,并将构件放入行列交 叉处的单元格中。为了使用这种布局,只需先创建构件,再用 grid 方法的选项 row 和 column 指定行、列编号即可。不需要预先定义表格的尺寸,Grid 布局管理器会根据构件的大小自动 调整:每一列的宽度由该列中最宽的构件决定,每一行的高度由该行最高的构件决定。行、列都是从 0 开始编号,row 的缺省值为当前下一空行,column 的缺省值总为 0。可 以在布置构件时指定不连续的行号或列号,这相对于预留了一些行列,但这些预留的行列是 不可见的,因为行列上没有构件存在,也就没有宽度和高度。
Grid 布局管理器的使用非常简单,可以说是进行图形界面布局的首选工具。先看一个简 单例子:
>>> Label(root,text="ID Number:").grid()
>>> Label(root,text="Name:").grid()
>>> Entry(root).grid(row=0,column=1)
>>> Entry(root).grid(row=1,column=1)
其中第一条语句使用缺省行号 0 和缺省列号 0 来安排标签“ID Number:”,第二条语句使用缺省行号 1 和缺省列号 0 来安排标签“Name”,后面两条语句在指定的位置安排录入框。布局效果如图 8.17 所示:
图 8.17 Grid 布局
从图 8.17 可以看出,标签构件在单元格里是居中放置的,如果觉得这种对齐方式不好看, 可以用 grid 方法的 sticky 选项来改变对齐方式。
Tkinter 中常利用“方位”概念来指定对齐方式,具体方位值包括 N、NE、E、SE、S、 SW、W、NW 和 CENTER(见图 8.18)。将 sticky 选项设置为某个方位,就表示将构件沿单元格的某条边或某个角对齐。
图 8.18 方位
如果构件比单元格小,未能填满单元格,则可以指定如何处理多余空间,比如在水平方 向或垂直方向拉伸构件以填满单元格。可以利用方位值的组合来使构件延伸,例如若将 sticky 设置为 E+W,则构件将在水平方向延伸,占满单元格的宽度;若设置为 E+W+N+S(或 NW+SE),则构件将在水平和垂直两个方向延伸,占满整个单元格。
如果想让一个构件占据多个单元格,可以使用 grid 方法的 rowspan 和 columnspan 选项来 指定在行和列方向上的跨度。例如,下面的语句序列能产生如图 8.19 所示的复杂布局:
>>> Label(root,text="ID Number:").grid(sticky=E)
>>> Label(root,text="Name:").grid(sticky=E)
>>> Entry(root).grid(row=0,column=1)
>>> Entry(root).grid(row=1,column=1)
>>> Checkbutton(root,text="Registered User").grid(
... columnspan=2,sticky=W)
>>> Label(root,text="X").grid(row=0,column=2,
... columnspan=2,rowspan=2,sticky=W+E+N+S)
>>> Button(root,text="Zoom In").grid(row=2,column=2)
>>> Button(root,text="Zoom Out").grid(row=2,column=3)
图 8.19 利用 Grid 进行复杂布局
下面再看一个利用框架来实现复杂界面结构的例子:
>>> f1 = Frame(root,width=100,height=100,bd=4,relief="groove")
>>> f1.grid(row=1,column=1,rowspan=2,sticky=N+S+W+E)
>>> Checkbutton(f1,text="PC").grid(row=1,sticky=W)
>>> Checkbutton(f1,text="Laptop").grid(row=2,sticky=W)
>>> f2 = Frame(root,width=100,height=50,bd=4,relief="groove")
>>> f2.grid(row=1,column=2,columnspan=2,sticky=N)
>>> b1 = Button(root,text="OK",width=6)
>>> b1.grid(row=2,column=2,sticky=E+W,padx=2)
>>> b2 = Button(root,text="Cancel",width=6)
>>> b2.grid(row=2,column=3,sticky=E+W,padx=2)
结果如图 8.20 所示。可以看出,这个例子大致实现了图 8.2 所要求的界面。
图 8.20 利用框架的布局
grid 方法的“逆”方法是 grid_forget 方法,意为将用 grid 布局的构件从界面中拿掉,从 而构件变成不可见。注意,这时构件作为对象仍然存在,只是未显示在界面中而已,我们随 时可以再次调用任何布局管理器方法来使构件可见。
Place 布局管理器*
Place 布局管理器直接指定构件在父构件中的位置坐标。为使用这种布局,只需先创建构 件,再调用构件的 place 方法,该方法的选项 x 和 y 用于设定坐标。父构件(窗口或框架) 的坐标系统以左上角为(0,0),x 方向向右,y 方向向下。
由于(x,y)坐标确定的是一个点,而子构件可看作是一个矩形,这个矩形怎么放置在一个 点上呢?Place 通过“锚点”来处理这个问题:利用方位值(见图 8.18)指定子构件的锚点, 再利用 place 方法的 anchor 选项来将子构件的锚点定位于父窗口的指定坐标处。利用这种精 确的定位,可以实现一个或多个构件在窗口中的各种对齐方式。anchor 的缺省值为 NW,即 构件的左上角。例如下面两条语句分别将两个标签置于根窗口的(0,0)和(199,199)处,定位锚 点分别是(默认的)NW 和 SE:
>>> Label(root,text="Hello").place(x=0,y=0)
>>> Label(root,text="World").place(x=199,y=199,anchor=SE)
下列语句序列围绕根窗口的点(100,100)按不同锚点布置了若干个按钮:
>>> Button(root,text="CCCCCCCCCCCCCCCCCC").place(x=100,y=100,
... anchor=CENTER)
>>> Button(root,text=" NW ").place(x=100,y=100,anchor=NW)
>>> Button(root,text="E").place(x=100,y=100,anchor=E)
>>> Button(root,text="W").place(x=100,y=100,anchor=W)
>>> Button(root,text=" SE ").place(x=100,y=100,anchor=SE)
以上语句的执行结果如图 8.21 所示。
图 8.21 利用 Place 布局
Place 布局管理器既可以像上例这样用绝对坐标指定位置,也可以用相对坐标指定位置。 相对坐标通过选项 relx 和 rely 来设置,取值范围为 0~1,表示构件在父构件中的相对比例 位置,如 relx=0.5 即表示父构件 x 方向上的二分之一处。相对坐标的好处是当窗口改变大小 时,构件位置将随之调整,不像绝对坐标固定不变。例如下面这条语句将标签布置于水平方 向四分之一、垂直方向二分之一处,定位锚点是 SW:
Label(root,text="Hello").place(relx=0.25,rely=0.5,anchor=SW)
除了指定构件位置,Place 布局管理器还可以指定构件大小。既可以通过选项 width 和 heightPlace 来定义构件的绝对尺寸,也可以通过选项 relwidth 和 relheight 来定义构件的相对 尺寸(即相对于父构件两个方向上的比例值)。
Place 是最灵活的布局管理器,但用起来比较麻烦,通常不适合对普通窗口和对话框进行 布局,其主要用途是实现复合构件的定制布局。
place 方法的“逆”方法是 place_forget 方法,意为将用 place 布局的构件从界面中拿掉, 从而构件变成不可见。注意,这时构件作为对象仍然存在,只是未显示在界面中而已,我们 随时可以再次调用任何布局管理器方法来使构件可见。