这节课我们来学习 Tkinter 的布局管理器,那什么是布局管理器呢?说白了,就是用于管理你的组件如何排列。Tkinter 提供了 3 大布局管理器:pack、grid 和 place。
对比 grid 管理器,pack 更适用于少量组件的排列,但它在使用上更加简单(就像我们前边所有的例子中,展示一个组件我们一般都直接使用 .pack(),多简单~)。如果你需要创建相对复杂的布局结构,那么建议是使用多个框架(Frame)结构构成,或者使用 grid 管理器实现。
注意:不要在同一个父组件中混合使用 pack 和 grid,因为 Tkinter 会很认真地在那儿计算到底先使用那个布局管理器......以至于你等了半个小时,Tkinter 还在那儿纠结不出结果!
我们来给大家举个例子:
我们常常会遇到一个情况就是 将一个组件放进另一个组件里,并且要求是填充另一个组件,那我们要怎么做呢?
我们先用 pack 来演示:
import tkinter as tk
root = tk.Tk()
listbox = tk.Listbox(root)
listbox.pack(fill = "both", expand = True)
for i in range(10):
listbox.insert("end", str(i))
root.mainloop()
其中,fill 选项是告诉窗口管理器该组件将填充整个分配给它的空间,"both" 表示同时横向和纵向扩展,"x" 表示横向,"y" 表示纵向;expand 选项是告诉窗口管理器将父组件的额外空间也填满。
刚才我们说了,pack 是按照组件的添加顺序依次排列的,那么它依次是纵向排列呢,还是横向排列呢?我们做一个演示:
import tkinter as tk
root = tk.Tk()
tk.Label(root, text="Red", bg="red", fg="white").pack(fill="x")
tk.Label(root, text="Green", bg="green", fg="black").pack(fill="x")
tk.Label(root, text="Blue", bg="blue", fg="white").pack(fill="x")
root.mainloop()
我们可以看到,是纵向填充的。如果想要组件横向挨个儿排放,你可以使用 side 选项:
import tkinter as tk
root = tk.Tk()
tk.Label(root, text="Red", bg="red", fg="white").pack(side = "left")
tk.Label(root, text="Green", bg="green", fg="black").pack(side = "left")
tk.Label(root, text="Blue", bg="blue", fg="white").pack(side = "left")
root.mainloop()
grid 管理器可以说是 Tkinter 这三个布局管理器中最灵活多变的。如果你只希望学习使用一个布局管理器,那么 grid 绝对是首选。当你在设计对话框的时候,使用 gird 尤其便捷。如果你此前一直在用 pack 构造窗口布局,那么学习完 grid 你会悔恨当初为啥不早学它。使用一个 grid 就可以简单的实现你用很多个框架和 pack 搭建起来的效果。
注意:不要在同一个父组件中混合使用 pack 和 grid,因为 Tkinter 会很认真地在那儿计算到底先使用那个布局管理器......以至于你等了半个小时,Tkinter 还在那儿纠结不出结果!
我们还是举例说明:
使用 grid 排列组件,只需告诉它你想要将组件放置的位置(行/列,row 选项指定行,cloumn 选项指定列)。此外,你并不用提前指出网格(grid 分布给组件的位置称为网格)的尺寸,因为管理器会自动计算。
import tkinter as tk
root = tk.Tk()
# column 默认值是 0
tk.Label(root, text="用户名").grid(row=0)
tk.Label(root, text="密码").grid(row=1)
tk.Entry(root).grid(row=0, column=1)
tk.Entry(root, show="*").grid(row=1, column=1)
root.mainloop()
默认情况下组件会居中显示在对应的网格里,你可以使用 sticky 选项来修改这一特性。该选项可以使用的值有 "e","w","s","n"(ewsn 分别表示东西南北,即上北下南左西右东)以及它们的组合。因此,我们可以通过 sticky = "w" 使得 Label 左对齐:
tk.Label(root, text="用户名").grid(row=0, sticky="w")
tk.Label(root, text="密码").grid(row=1, sticky="w")
有时候你可能需要用几个网格来放置一个组件,可以做到吗?当然可以,你只需要指定 rowspan 和 columnspan 就可以实现跨行和跨列的功能:
import tkinter as tk
root = tk.Tk()
# column 默认值是 0
tk.Label(root, text="用户名").grid(row=0, sticky="w")
tk.Label(root, text="密码").grid(row=1, sticky="w")
tk.Entry(root).grid(row=0, column=1)
tk.Entry(root, show="*").grid(row=1, column=1)
photo = tk.PhotoImage(file="logo.gif")
tk.Label(root, image=photo).grid(row=0, column=2, rowspan=2, padx=5, pady=5) #跨两行
tk.Button(text="提交", width=10).grid(row=2, columnspan=3, pady=5) #跨3列
root.mainloop()
通常情况下不建议使用 place 布局管理器,因为对比起 pack 和 grid,place 要做更多的工作。不过存在即合理,place 在一些特殊的情况下可以发挥妙用。请看下边例子。
比如 我想 :将子组件显示在父组件的正中间:
import tkinter as tk
root = tk.Tk()
def callback():
print("正中靶心")
tk.Button(root, text="点我", command=callback).place(relx=0.5, rely=0.5, anchor="center")
root.mainloop()
relx 和 rely 就是相对于 父组件的水平位置 和 垂直位置; anchor="center" 表示居中显示。
在某种情况下,或许你希望一个组件可以覆盖另一个组件,那么 place 又可以派上用场了。下边例子我们演示用 Button 覆盖 Label 组件:
import tkinter as tk
root = tk.Tk()
photo = tk.PhotoImage(file = "Python.gif")
tk.Label(root, image = photo).pack()
def callback():
print("正中靶心")
tk.Button(root, text="点我", command=callback).place(relx=0.5, rely=0.5, anchor="center")
root.mainloop()
relx 和 rely 选项指定的是相对于父组件的位置,范围是 00 ~ 1.0,因此 0.5 表示位于正中间。那么 relwidth 和 relheight 选项则是指定相对于父组件的尺寸:
import tkinter as tk
root = tk.Tk()
tk.Label(root, bg="red").place(relx=0.5, rely=0.5, relheight=0.75, relwidth=0.75, anchor="center")
tk.Label(root, bg="yellow").place(relx=0.5, rely=0.5, relheight=0.5, relwidth=0.5, anchor="center")
tk.Label(root, bg="green").place(relx=0.5, rely=0.5, relheight=0.25, relwidth=0.25, anchor="center")
root.mainloop()
上图无论你如何拉伸改变窗口,三个 Label 的尺寸均会跟着改变(神同步~)。x 和 y 选项用于设置偏移(像素),如果同时设置 relx(rely)和 x(y),那 place 将优先计算 relx 和 rely,然后再实现 x 和 y 指定的偏移值。
关于这三个布局管理器的方法和其他选项,可以查看这三个组件的详解。(有分别介绍这三个组件全部内容的博客,下面是链接):
Tkinter 布局管理器(一):pack
Tkinter 布局管理器(二):grid
Tkinter 布局管理器(三):place