当我们在开发一个GUI程序的时候,布局管理发挥着非常重要的作用,它指的是通过管理控件在窗口中的位置(排版),从而实现对窗口和控件布局的目的。
一个优秀的GUI图形用户界面,更像是一个艺术品,它会给用户非常良好的感官体验,因此布局管理不单单是简单的程序代码,更需要以“美观”的角度去控制每一个细节,这样才能吸引更多的用户使用程序。
在前面的教程中,我们介绍的所有Tkinter控件中,控件布局都是使用pack()方法布局的。其实,pack()是一种较为简单的布局方法,在不使用任何参数的情况下,它会将控件以添加时的先后顺序,自上而下,一行一行的进行排列,并且默认居中显示。
Tkinter提供了三种常用的布局管理方法(也称布局管理器),分别是pack()、grid()和place(),用于组织和管理控件的布局。这里需要注意的是,在同一个主窗口中,只能使用一种布局方式,要么控件布局都使用pack(),要么都用grid(),要么都用place(),三种布局不能混合使用。三种布局管理器的使用方法如下表所示:
方法 | 说明 |
---|---|
pack() | 按照控件的添加顺序依次进行排列,缺点是灵活性较差 |
grid() | 以行和列(网格)形式对控件进行排列,此种方法使用起来较为灵活 |
place() | 可以指定组件大小以及摆放位置,三个方法中最为灵活的布局方法 |
1、pack()布局方法
在Tkinter中pack()方法适用于简单的布局,在不添加参数的默认情况下,使用pack()方法可以将控件按照添加的先后顺序,自上而下自动排列。pack()方法的常用参数如下表所示:
名称 | 描述 | 取值范围 |
---|---|---|
expand | 控制组件是否填充父组件的剩余空间。 | "yes", 自然数, "no", 0 (默认值为 "no" 或 0) |
fill | 填充 x(y) 方向上的空间。 | "x", "y", "both" (默认值为待选) |
ipadx | 组件内部在 x(y) 方向上的填充空间大小。 | 非负浮点数 (默认值为0.0) |
ipady | 组件内部在 y 方向上的填充空间大小。 | 非负浮点数 (默认值为0.0) |
padx | 组件外部在 x 方向上的填充空间大小。 | 非负浮点数 (默认值为0.0) |
pady | 组件外部在 y 方向上的填充空间大小。 | 非负浮点数 (默认值为0.0) |
side | 定义组件停靠在父组件的哪一边。 | "top", "bottom", "left", "right" (默认为 "top") |
before | 将组件在所选组件之前 pack。 | 已经pack后的组件对象 |
after | 将组件在所选组件之后 pack。 | 已经pack后的组件对象 |
in_ | 将组件作为所选组件的子组件。 | 已经pack后的组件对象 |
anchor | 对齐方式,如左对齐"w",右对齐"e",顶对齐"n",底对齐"s" | "n", "s", "w", "e", "nw", "sw", "se", "ne", "center" (默认为"center") |
下面演示Tkinter中pack()方法的布局管理,例如:
动手练一练:
# 导入tkinter模块,用于创建GUI应用程序
import tkinter as tk
# 创建一个主窗口对象
app = tk.Tk()
# 设置窗口标题,也就是窗口的名字
app.title("pack()方法布局示例")
# 设置窗口大小为300x300
app.geometry("300x300")
# 创建三个标签控件
# 创建标签1,背景色为红色,字体颜色为白色
label1 = tk.Label(app, text="标签 1", bg="red", fg="white")
# 创建标签2,背景色为黄色,字体颜色为黑色
label2 = tk.Label(app, text="标签 2", bg="yellow", fg="black")
# 创建标签3,背景色为绿色,字体颜色为黄色
label3 = tk.Label(app, text="标签 3", bg="green", fg="yellow")
# 创建标签4,背景色为蓝色,字体颜色为白色
label4 = tk.Label(app, text="标签 4", bg="blue", fg="white")
# 创建标签5,背景色为粉红色,字体颜色为黑色
label5= tk.Label(app, text="标签 5", bg="pink", fg="black")
# 使用pack()方法进行布局
# 没有设置参数,默认以参数side="top"方式排列,标签1将显示在窗口顶部
label1.pack()
# 设置对齐方式,左对齐"w",标签2将摆放在主窗口左面
label2.pack(anchor="w")
# 设置控件从左到右依次排列,外边间距为10像素,标签3将显示在窗口左侧
label3.pack(side="left", padx=10, pady=10)
# 设置控件从右向左依次排列,标签4将显示在窗口右侧
label4.pack(side="right")
# 设置控件从下到上依次排列,标签5将显示在窗口底部
# 标签5将填充剩余空间,并且在水平和垂直方向上扩展以填充任何额外空间
label5.pack(side="bottom", expand="yes", fill="both")
# 开启主循环,让窗口处于显示状态
app.mainloop()
执行以上代码,运行结果如下图:
(1)pack()方法中side参数详解
在pack()方法布局中,任何控件都可以实现水平排列,也可实现垂直排列。pack()方法中的side参数用于设置组件水平展示或者垂直展示,设置如下所示:
side="top":指组件从上到下依次排列,这是side参数的默认值。
side="bottom":指组件从下到上依次排列。
side="left":指组件从左到右依次排列。
side="right":指组件从右向左依次排列。
(2)pack()方法中anchor参数详解
anchor参数指定控件摆放主窗口的位置,前提是主窗口的可用空间大于组件大小,参数取值如下:
anchor="n":北方(上方)
anchor="s":南方(下方)
anchor="e":东方(右侧)
anchor="w":西方(左侧)
anchor="nw":西北方(左上)
anchor="ne":东北方(右上)
anchor="sw":西南方(左下)
anchor="se":东南方(右下)
anchor="center" 或anchor="c":中心位置
anchor参数遵循“上北下南,左西右东”的原则,这些方向实现的布局效果,如下图所示:
2、grid()布局方法
前面我们已经详细介绍了,pack()方法布局方式适合于简单的布局,在同一主窗口中,只能进行上下左右四个方向的布局,而grid()方法则适用于精确控制组件位置的布局。grid()方法是一种基于网格式的布局管理方法,相当于把主窗口看成了一张由“行”和“列”组成的表格。当使用grid()方法进行布局时,表格内的每个单元格都可以放置一个控件,从而实现对界面的精确布局管理。
注意:这里介绍的“表格”是一个虚拟二维表格,方便大家理解,其实主窗口中并不会因为使用了grid()方法布局而增加一个表格。
grid()方法的常用参数如下表所示:
名称 | 描述 |
---|---|
row | 指定组件所在的行,窗体最上面为起始行,默认为第0行 |
column | 指定组件所在的列,窗体最左边的为起始列,默认为第0列 |
rowspan | 组件跨越的行数,默认为跨越1行,通过该参数可以合并一列中多个领近单元格。 |
columnspan | 组件跨越的列数,默认为跨越1列,通过该参数可以合并一行中多个领近单元格。 |
sticky | 控制组件在单元格中的对齐方式,参数值为"n"(上), "s"(下), "w"(左), "e"(右), "nw"(左上), "sw"(左下), "se"(右下), "ne"(右上)或"center"(居中),默认为"center"。 |
padx,pady | 用于控制外边距,在单元格外部,左右、上下方向上填充指定大小的空间。 |
ipadx,ipady | 用于控制内边距,在单元格内部,左右、上下方向上填充指定大小的空间。 |
下面演示Tkinter中grid()方法的简单布局管理,例如:
动手练一练:
# 导入tkinter模块,用于创建GUI应用程序
import tkinter as tk
# 创建一个主窗口对象
app = tk.Tk()
# 设置窗口标题,也就是窗口的名字
app.title("grid()方法布局示例")
# 设置窗口大小为300x200
app.geometry("300x200")
# 禁止窗口大小调整
app.resizable(False, False)
# 创建按钮控件1,放置在网格的第0行第0列
btn1 = tk.Button(app, text='0行0列')
btn1.grid(row=0, column=0)
# 创建按钮控件2,放置在网格的第0行第1列
btn2 = tk.Button(app, text='0行1列')
btn2.grid(row=0, column=1)
# 创建按钮控件3,放置在网格的第1行第0列
btn3 = tk.Button(app, text='1行0列')
btn3.grid(row=1, column=0)
# 创建按钮控件4,放置在网格的第1行第1列
btn4 = tk.Button(app, text='1行1列')
btn4.grid(row=1, column=1)
# 创建按钮控件5,放置在网格的第2行第0列同时占据两列网格
# 设置单元格内部左右距离为20像素
btn5 = tk.Button(app, text="占据两列")
btn5.grid(row=2, column=0, columnspan=2, ipadx=20)
# 创建按钮控件6,放置在网格的第0行第2列同时占据两行网格
# 设置单元格内部上下距离为15像素
btn6 = tk.Button(app, text='占据两行')
btn6.grid(row=0, column=2, rowspan=2, ipady=15)
# 开启主循环,让窗口处于显示状态
app.mainloop()
执行以上代码,运行结果如下图:
从上面的运行结果可以看出,用grid()方法排列按钮控件,可以设想主窗口有一个3x3的表格,起始行、起始列序号均为0,第二行、第二列序号均为1,以此类推。从效果图可以看出,grid()方法相比pack()方法来说要更加灵活,以网格的方式对组件进行布局管理,让整个布局显得非常简洁、优雅。
但是这里需要注意的是,在同一个程序主窗口中不能同时使用pack()和grid()方法,这两个方法只能二选一,否则程序会运行错误。
(1)grid()方法输出复杂的数据表格
grid()方法可以帮助开发者在Tkinter窗口中实现多种复杂的界面布局,例如网格状的数据表格、表单等。通过使用sticky参数,可以精确控制组件在网格单元格中的对齐方式,使布局更加灵活。例如:
动手练一练:
# 导入tkinter模块,用于创建GUI应用程序
import tkinter as tk
# 创建一个主窗口对象
app = tk.Tk()
# 设置窗口标题,也就是窗口的名字
app.title("grid布局输出数据表格")
# 设置窗口大小为300x115
app.geometry("300x115")
# 禁止窗口大小调整
app.resizable(False, False)
# 添加学生信息列表
students = [
("2024001", "张三", 82, 98),
("2024002", "李四", 96, 92),
("2024003", "王五", 75, 84),
("2024004", "赵六", 93, 86),
]
# 创建表格中的单元格
# 第一行(表头)
# 创建一个标签,显示文本“学号”,背景色为白色,宽度为10
label1 = tk.Label(app, text="学号", bg="#88eafc", width=10)
# 创建一个标签,显示文本“姓名”,背景色为白色,宽度为10
label2 = tk.Label(app, text="姓名", bg="#88eafc", width=10)
# 创建一个标签,显示文本“数学成绩”,背景色为白色,宽度为10
label3 = tk.Label(app, text="数学成绩", bg="#88eafc", width=10)
# 创建一个标签,显示文本“语文成绩”,背景色为白色,宽度为10
label4 = tk.Label(app, text="语文成绩", bg="#88eafc", width=10)
# 将label1放置在网格的第0行第0列,使其在北、南、东、西方向上拉伸
# sticky="nsew"表示组件会被拉伸以占满整个单元格
# 这意味着组件会被拉伸填满整个行和列。
label1.grid(row=0, column=0, sticky="nsew")
# 将label2放置在网格的第0行第1列,使其在北、南、东、西方向上拉伸
label2.grid(row=0, column=1, sticky="nsew")
# 将label3放置在网格的第0行第2列,使其在北、南、东、西方向上拉伸
label3.grid(row=0, column=2, sticky="nsew")
# 将label4放置在网格的第0行第3列,使其在北、南、东、西方向上拉伸
label4.grid(row=0, column=3, sticky="nsew")
# 循环创建学生信息行,enumerate()方法遍历数据对象,并组合成一个索引序列
for i, (student_id, name, math_score, chinese_score) in enumerate(students):
# 创建一个标签控件,显示学生学号
label5 = tk.Label(app, text=student_id, bg="white", width=10)
# 创建一个标签控件,显示学生姓名
label6 = tk.Label(app, text=name, bg="white", width=10)
# 创建一个标签控件,显示学生数学成绩
label7 = tk.Label(app, text=str(math_score), bg="white", width=10)
# 创建一个标签控件显示学生语文成绩
label8 = tk.Label(app, text=str(chinese_score), bg="white", width=10)
# 将label5放置在网格的第i+1行第0列,使其在北、南、东、西方向上拉伸
label5.grid(row=i + 1, column=0, sticky="nsew")
# 将label6放置在网格的第i+1行第1列,使其在北、南、东、西方向上拉伸
label6.grid(row=i + 1, column=1, sticky="nsew")
# 将label7放置在网格的第i+1行第2列,使其在北、南、东、西方向上拉伸
label7.grid(row=i + 1, column=2, sticky="nsew")
# 将label8放置在网格的第i+1行第3列,使其在北、南、东、西方向上拉伸
label8.grid(row=i + 1, column=3, sticky="nsew")
# 开启主循环,让窗口处于显示状态
app.mainloop()
执行以上代码,运行结果如下图:
(2)grid()方法创建简单的登录窗口
在Python开发软件时,经常会用到用户的登录界面,使用Tkinter的grid()布局方法可以快速制作一个简单的登录界面。我们可以通过grid()方法的网格状形式来排列标签、输入框和按钮,并通过sticky参数可以设置“登录”和“退出”按钮的方位,该参数默认将控件设置居中,其他参数值有n/s/w/e(上/下/左/右),而且可以组合在一起使用,比如nw/we/se/sw/ne等。
以下是一个简单的登录程序示例代码:
动手练一练:
# 导入tkinter模块,用于创建GUI应用程序
import tkinter as tk
from tkinter import messagebox
# 创建一个主窗口对象
app = tk.Tk()
# 设置窗口标题,也就是窗口的名字
app.title("grid布局创建登录窗口")
# 设置窗口大小:宽x高,并指定窗口在屏幕上的位置为(200, 200)
# 这里的乘号不是“*”,而是小写英文字母“x”
app.geometry("300x140+200+200")
# 禁止窗口大小调整
app.resizable(False, False)
# 编写一个登录的回调函数
def login():
# 获取“用户名”输入框内的值
username = entry_username.get()
# 获取“密码”输入框内的值
password = entry_password.get()
# 判断输入的用户名和密码是否正确
if username == "abc" and password == "123":
# 提示登录成功对话框
messagebox.showinfo("成功", "登录成功")
else:
# 提示错误对话框
messagebox.showerror("错误", "用户名或密码错误")
# 将“用户名”标签布置在第0行第0列
label_username = tk.Label(app, text="用户名:")
label_username.grid(row=0, column=0, padx=10, pady=10)
# 将“密码”标签布置在第1行第0列
label_password = tk.Label(app, text="密码:")
label_password.grid(row=1, column=0, padx=10, pady=10)
# 在第0行第1列创建“用户名”输入框控件。
entry_username = tk.Entry(app)
entry_username.grid(row=0, column=1, padx=10, pady=10)
# 在第1行第1列创建“密码”输入框控件,并以“*”的形式显示密码
entry_password = tk.Entry(app, show="*")
entry_password.grid(row=1, column=1, padx=10, pady=10)
# 使用grid()的函数来布局,并分别控制“登录”和“退出”按钮的显示位置
button_login = tk.Button(app, text="登录", width=10, command=login)
button_login.grid(row=3, column=0, sticky="w", padx=10, pady=5)
quit_login = tk.Button(app, text="退出", width=10, command=app.quit)
quit_login.grid(row=3, column=1, sticky="e", padx=10, pady=5)
# 开启主循环,让窗口处于显示状态
app.mainloop()
执行以上代码,运行结果如下图:
在这个例子中,用户名被硬编码为“abc”,密码被硬编码为“123”。当用户点击登录按钮时,会检查输入的用户名和密码是否正确。如果正确,会显示一个信息框提示登录成功;如果错误,则显示一个错误框。在实际应用中,应该从数据库或其他认证系统获取这些登录信息。用户提交登录信息后,程序将验证输入并通过消息框显示结果。
3、place()布局方法
place()布局方法是Tkinter支持的第三种布局管理器,它允许程序员直接指定控件的位置和大小。与grid和pack两种布局方法相比,采用place()方法布局会更加精细化,可以直接指定控件在窗体内的绝对位置,或者相对于其他控件定位的相对位置。
place布局适用于更加复杂的,需要准确摆放控件的窗体中。这种布局不是很常用,因为使用比较麻烦,需要提供x、y坐标以及组件的width和height(以像素为单位)。
place布局管理器的常用属性如下表所示:
属性 | 说明 |
---|---|
anchor | 定义控件在窗体内的方位,参数值"n"(上), "s"(下), "w"(左), "e"(右), "nw"(左上), "sw"(左下), "se"(右下), "ne"(右上)或"center"(居中),默认值是"nw" |
bordermode | 定义控件的坐标是否要考虑边界的宽度,参数值为outside(排除边界)或inside(包含边界),默认值inside。 |
x、y | 定义控件在根窗体中水平和垂直方向上的起始绝对位置 |
relx、rely | 定义控件相对于根窗口(或其他控件)在水平和垂直方向上的相对位置(即位移比例),取值范围在0.0~1.0之间。可设置“in_”参数项,相对于某个其他控件的位置 |
height、width | 控件自身的高度和宽度(单位为像素) |
relheight、relwidth | 控件高度和宽度相对于根窗体高度和宽度的比例,取值也在0.0~1.0之间 |
下面演示Tkinter中place()方法的简单布局管理,例如:
动手练一练:
# 导入tkinter模块,用于创建GUI应用程序
import tkinter as tk
# 创建一个主窗口对象
app = tk.Tk()
# 设置窗口标题,也就是窗口的名字
app.title("place方法布局示例")
# 设置窗口大小:宽x高,并指定窗口在屏幕上的位置为(200, 200)
# 这里的乘号不是“*”,而是小写英文字母“x”
app.geometry("300x200+200+200")
# 创建标签1
label1 = tk.Label(app, text="标签1", bg='blue', fg='white')
# 设置第一个标签位于距离窗体左上角的位置(40,40)和其大小(width,height)
# 注意这里(x,y)位置坐标指的是标签左上角的位置(以nw左上角进行绝对定位,默认为nw)
label1.place(x=40,y=40, width=60, height=30)
# 设置标签2
label2 = tk.Label(app, text="标签2",bg='purple',fg='white')
# anchor="ne"以标签的右上角进行绝对值定位,第二个标签右上角的位置距离窗体左上角的(180,80)
label2.place(x=180, y=80, anchor="ne", width=60, height=30)
# 设置标签3
label3 = tk.Label(app, text="标签3",bg='green',fg='white')
# 设置水平起始位置相对于窗体水平距离的0.7倍,垂直的绝对距离为80,大小为60,30
label3.place(relx=0.7, y=80, width=60, height=30)
# 设置标签4
label4 = tk.Label(app, text="标签4",bg='gray',fg='white')
# 设置水平起始位置相对于窗体水平距离的0.01倍,垂直的绝对距离为80
# 设置标签高度为窗体高度比例的0.4倍,宽度为80
label4.place(relx=0.01, y=80, relheight=0.4, width=80)
# 开启主循环,让窗口处于显示状态
app.mainloop()
执行以上代码,运行结果如下图:
从运行结果可以看出,place布局管理器虽然看起来简单,但是当控件的数量多了以后,坐标的计算和维护都会变得很复杂。正是由于这个原因,Tkinter开发的程序应用很少会使用place布局方法。
使用Tkinter的place布局方法同样可以制作一个简单的登录界面,以下是一个简单的登录程序示例代码:
动手练一练:
# 导入tkinter模块,用于创建GUI应用程序
import tkinter as tk
from tkinter import messagebox
# 创建一个主窗口对象
app = tk.Tk()
# 设置窗口标题,也就是窗口的名字
app.title("grid布局创建登录窗口")
# 设置窗口大小:宽x高,并指定窗口在屏幕上的位置为(200, 200)
# 这里的乘号不是“*”,而是小写英文字母“x”
app.geometry("400x150+200+200")
# 禁止窗口大小调整
app.resizable(False, False)
# 编写一个登录的回调函数
def login():
# 获取“用户名”输入框内的值
username = entry_username.get()
# 获取“密码”输入框内的值
password = entry_password.get()
# 判断输入的用户名和密码是否正确
if username == "abc" and password == "123":
# 提示登录成功对话框
messagebox.showinfo("成功", "登录成功")
else:
# 提示错误对话框
messagebox.showerror("错误", "用户名或密码错误")
# 创建一个标签,显示文本“用户名:”
label_username = tk.Label(app, text="用户名:")
# 使用place布局管理器,将标签放置在窗口的(80, 20)位置
label_username.place(x=80, y=20)
# 创建一个标签,显示文本“密码:”
label_password = tk.Label(app, text="密码:")
# 使用place布局管理器,将标签放置在窗口的(80, 60)位置
label_password.place(x=80, y=60)
# 创建一个文本输入框,用于输入用户名
entry_username = tk.Entry(app)
# 将输入框放置在窗口的(130, 20)位置,宽度为200
entry_username.place(x=130, y=20, width=200)
# 创建一个文本输入框,用于输入密码,输入时显示为星号(*)
entry_password = tk.Entry(app, show="*")
# 将输入框放置在窗口的(130, 60)位置,宽度为200
entry_password.place(x=130, y=60, width=200)
# 创建登录按钮,点击时弹出提示对话框
button_login = tk.Button(app, text="登录", command=login)
# 使用place布局管理器,将按钮放置在窗口的(110, 100)位置,宽度为100
button_login.place(x=110, y=100, width=100)
# 创建退出按钮,点击时关闭窗口
quit_login = tk.Button(app, text="退出", command=app.quit)
# 使用place布局管理器,将按钮放置在窗口的(230, 100)位置,宽度为100
quit_login.place(x=230, y=100, width=100)
# 开启主循环,让窗口处于显示状态
app.mainloop()
执行以上代码,运行结果如下图:
从这个例子中可以看出,place()布局方法和grid()布局方法都可以创建登录窗口,但是控件存放的位置两者略有不同,place()布局方法需要手动计算每个控件的位置。所以在程序开发中,当需要对控件的位置进行精细控制,或者布局较为固定且不依赖于窗口大小变化时,place()布局方法是一个好选择,它适合于需要精确控制控件位置和大小的复杂布局。