Tkinter网格:如何使控件不紧贴在一起
我正在尝试创建两个标签(Label)控件,分别放在我的测试界面的左上角和右上角。问题是这两个控件紧挨在一起,我希望它们之间有一些空隙。
在我的研究中,我发现有人建议使用sticky、padx和pady这些选项。但是无论我在.grid()中传入什么参数,我似乎都无法在控件之间创造出空隙。我明白了,不管两个控件之间有多少行和列,如果这些行/列是空的,就好像它们不存在一样,控件就会看起来粘在一起。
使用.grid()方法,我该如何放置控件才能让它们不再粘在一起呢?
这是我目前的代码:
#!/usr/bin/python
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myParent = parent
self.main_container = Frame(parent)
self.main_container.grid(row=0, rowspan=2, column=0, columnspan=4)
self.top_frame = Frame(self.main_container)
self.top_frame.grid(row=0, column=0, columnspan=4)
self.top_left = Frame(self.top_frame)
self.top_left.grid(row=0, column=0, columnspan=2)
self.top_right = Frame(self.top_frame)
self.top_right.grid(row=0, column=2, columnspan=2)
self.bottom_frame = Frame(self.main_container)
self.bottom_frame.grid(row=2, column=0, columnspan=4)
self.top_left_label = Label(self.top_left, text="Top Left")
self.top_left_label.grid(row=0, column=0, sticky='W', padx=2, pady=2)
self.top_right_label = Label(self.top_right, text="Top Right")
self.top_right_label.grid(row=0, column=4, sticky='E', padx=2, pady=2)
self.text_box = Text(self.bottom_frame, height=5, width=40)
self.text_box.grid(row=0, column=0)
root = Tk()
root.title("Test UI")
myapp = MyApp(root)
root.mainloop()
~~更新~~
我尝试了以下方法,但没有成功:
self.top_left = Frame(self.top_frame)
self.top_left.grid(row=0, column=0, columnspan=2)
for c in range(2):
self.top_left.columnconfigure(c, weight=2)
self.top_right = Frame(self.top_frame)
self.top_right.grid(row=0, column=2, columnspan=2)
for c in range(2):
self.top_right.columnconfigure(c, weight=2)
5 个回答
这里有一个简单的方法:
- 首先,可以在纸上或者用你喜欢的工具设计你的界面。
- 使用网格布局管理器。
- 如果你想在某个地方留空白,可以使用布局中的columnspan或rowspan属性,配合sticky属性。columnspan或rowspan可以让你把网格中的多个单元格分配给一个界面组件,而sticky属性则可以让这个组件贴在某一侧,另一侧留空。
如果你在 top_left
和 top_right
的构造函数里都加上 bg = "red"
,你会发现这些 Frames
被固定在中间,即使你使用了 sticky
选项。如果它们只会放一个小部件,我建议你不要使用这些 Frames
。
#!/usr/bin/python
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.top_left_label = Label(parent, text="Top Left")
self.top_left_label.grid(row=0, column=0, padx=2, pady=2, sticky=N+S+W)
self.top_right_label = Label(parent, text="Top Right")
self.top_right_label.grid(row=0, column=1, padx=2, pady=2, sticky=N+S+E)
self.text_box = Text(parent, height=5, width=40)
self.text_box.grid(row=1, column=0, columnspan=2)
root = Tk()
root.title("Test UI")
myapp = MyApp(root)
root.mainloop()
你需要使用 grid_columnconfigure
来给中间的列一些“权重”。这样,它们就会比其他列更容易扩展和收缩,以填充你多出来的空间。不过,在你的情况下,其实没必要使用 grid。因为你界面上的所有东西都是沿着特定的边对齐的,使用 pack
更自然。
我会先介绍如何使用 pack
和 grid
,先从 pack
开始,因为它是最简单的。即使你坚持要使用 grid
,也请先看看下一部分,了解如何把一个大布局问题拆分成多个小问题。
分而治之
在 Tkinter 中,布局的最佳方法是“分而治之”。从最外层的控件开始,按照你想要的样子调整它们。然后,逐个解决这些控件。
在你的情况下,最外层的控件是主容器。因为它是窗口中唯一的控件,所以使用 pack
是让它填满整个容器的最简单方法。你也可以使用 grid
,但这需要多一点工作:
self.main_container = Frame(parent. background="bisque")
self.main_container.pack(side="top", fill="both", expand=True)
为了帮助你在开发时更好地可视化,可以暂时给你的框架设置不同的颜色。把上面的代码加到你的 __init__
方法中,运行你的应用程序,然后调整窗口大小,看看主框架是否能正确地扩展和收缩。
接下来,你有两个框架——top_frame 和 bottom_frame。根据它们的名字和你尝试使用 grid 的方式,我猜它们应该在 x 方向上填满界面。而且,我猜 top_frame 是某种工具栏,bottom_frame 是你界面的“主”部分。因此,让我们让底部的控件占据所有的额外空间。
因为这些框架是上下堆叠的,所以 pack
仍然是最自然的选择。只需添加以下代码——并且只添加这段代码——以确保这些区域占据你预期的窗口部分,并且具有正确的调整大小行为。
self.top_frame = Frame(self.main_container, background="green")
self.bottom_frame = Frame(self.main_container, background="yellow")
self.top_frame.pack(side="top", fill="x", expand=False)
self.bottom_frame.pack(side="bottom", fill="both", expand=True)
接下来是标签的框架。它们同样占据了容器边缘的空间,所以使用 pack
是最合适的。再次添加以下代码,运行你的程序,确保一切仍然能够正确调整大小并填充窗口的正确部分:
self.top_left = Frame(self.top_frame, background="pink")
self.top_right = Frame(self.top_frame, background="blue")
self.top_left.pack(side="left", fill="x", expand=True)
self.top_right.pack(side="right", fill="x", expand=True)
接下来是你的“角落”标签。由于容器只有一行控件,使用 pack
会很简单。因为你想把标签放在角落,所以我们会为每个标签设置不同的 sticky
属性:
self.top_left_label = Label(self.top_left, text="Top Left")
self.top_right_label = Label(self.top_right, text="Top Right")
self.top_left_label.pack(side="left")
self.top_right_label.pack(side="right")
最后,你有文本控件。它填满了整个底部框架,所以再次使用 pack
是个好选择:
self.text_box = Text(self.bottom_frame, height=5, width=40, background="gray")
self.text_box.pack(side="top", fill="both", expand=True)
使用 pack 还是 grid?
你在原始代码中使用了 grid,并询问如何修复它。那我为什么在我的例子中使用 pack
呢?
使用 pack
时,所有的配置选项都可以在一个调用中完成。而使用 grid
时,除了把控件放入容器外,你还必须为不同的列和行设置“权重”,以便它们能够正确调整大小。当你只是把控件堆叠在一起或在一行中对齐时,pack
更容易使用。
在我的 GUI 中,我几乎总是结合使用 grid
和 pack
。它们都很强大,并且在不同的方面表现出色。唯一需要记住的——这非常重要——是你不能在同一个父容器中同时使用它们。以你的代码为例,你不能在 top_left 框架中使用 pack
,在 top_right 框架中使用 grid
,因为它们共享同一个父容器。不过,你可以在同一个应用程序中混合使用它们。
再次使用 grid
好吧,也许你真的想使用 grid
:也许这是一个学校作业,或者你只是想一次专注于一个几何管理器。没问题。以下是我会怎么做。同样,你必须分而治之:
从主框架开始。用以下几行代码替换我们打包主容器的那一行。注意,我们必须在父容器上配置行和列,而不是我们创建的框架上。
self.main_container.grid(row=0, column=0, sticky="nsew")
self.myParent.grid_rowconfigure(0, weight=1)
self.myParent.grid_columnconfigure(0, weight=1)
好的,现在处理顶部和底部框架。去掉 pack
,添加以下几行。你仍然只有一列,但这次有几行。注意哪个行的权重是 1:
self.top_frame.grid(row=0, column=0, sticky="ew")
self.bottom_frame.grid(row=1, column=0,sticky="nsew")
self.main_container.grid_rowconfigure(1, weight=1)
self.main_container.grid_columnconfigure(0, weight=1)
角落框架——终于,一个有多于一列的容器!让我们在中间创建第三列来填补所有的空隙。用以下代码替换 pack
语句,注意哪些被赋予了“权重”:
self.top_left.grid(row=0, column=0, sticky="w")
self.top_right.grid(row=0, column=2, sticky="e")
self.top_frame.grid_columnconfigure(1, weight=1)
接下来是它们框架中的标签。因为我们不需要它们扩展,所以可以保持默认的零权重。你可能会觉得两个标签都在第零列有点奇怪。记住,它们在不同的父容器中,而且它们是各自父容器中唯一的控件,所以每个父容器中只有一列:
self.top_left_label.grid(row=0, column=0, sticky="w")
self.top_right_label.grid(row=0, column=0, sticky="e")
最后,我们有文本控件,它是底部框架中唯一的控件。用以下代码替换最后的 pack
语句:
self.text_box.grid(row=0, column=0, sticky="nsew")
self.bottom_frame.grid_rowconfigure(0, weight=1)
self.bottom_frame.grid_columnconfigure(0, weight=1)
总结
布局控件很简单,但你必须有条理。考虑你在做什么,把控件组织成组,然后逐个解决这些组。当你这样做时,应该能清楚地看出应该使用哪个几何管理器。
如你所见,grid
需要更多的代码行,但当你确实有一个网格时,它是正确的选择。在你的情况下,你已经把 GUI 分成了几个部分,所以即使最终结果是一个网格,每个部分都是堆叠在一起或靠左或靠右边缘的。在这些情况下,pack
更容易使用,因为你不需要担心行和列的权重。