Tkinter 底部文本框与滚动条各占一半宽度,如何修复布局?
将文本框所在列设为 weight=1,滚动条所在列设为 weight=0,并让外层各级 Frame 都能横向展开。滚动条使用 sticky="ns",就能在右侧保持自身所需的窄宽度,同时沿文本框高度拉伸。
原代码中的多层容器没有配置伸展。不过,仅凭这段代码,无法确定界面为什么恰好呈现为“各占一半”;weight=10 和 weight=1 本身不会要求两列等宽。
为什么现有设置没有达到预期?
使用 grid 布局时,需要考虑父容器如何分配空间,以及控件如何使用分配到的空间。
columnconfigure(..., weight=...)决定列如何参与额外空间的分配。10:1是额外空间的分配权重,不能直接理解为最终列宽比例。sticky="ew"让控件横向填满所在单元格;sticky="nsew"让控件同时横向和纵向填满单元格。- 配置只对当前容器中的直接子控件有效。内层列有权重,外层容器也不会自动展开。
原代码中,self.textframe 的两列都设置了正权重,因此滚动条所在列也会分到额外宽度。滚动条使用 sticky="e",只会靠右对齐,不会填满单元格的高度。
另外,logbox_frame 没有为承载 logbox 的列设置权重,logbox 自身的 grid() 没有设置 sticky,self 也没有为承载 self.textframe 的行列设置权重。这些设置都会影响内部布局能否跟随窗口展开。
可直接运行的修复示例
下面的示例让底部日志区保持内容请求的高度,上方区域使用其余高度。文本框占用底部剩余宽度,滚动条保留自身请求宽度。示例也补齐了原粘贴代码中缺失的缩进。
import tkinter as tk
def create_root():
root = tk.Tk()
root.title("Aghast")
root.geometry("1400x800")
root.configure(background="dark grey")
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=0)
return root
class Logbox(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
# 允许 textframe 填满当前组件。
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.textframe = tk.Frame(self)
self.textframe.grid(row=0, column=0, sticky="nsew")
self.textframe.rowconfigure(0, weight=1)
self.textframe.columnconfigure(0, weight=1)
self.textframe.columnconfigure(1, weight=0)
self.scrollbar = tk.Scrollbar(
self.textframe,
orient="vertical",
)
self.scrollbar.grid(row=0, column=1, sticky="ns")
self.textbox = tk.Text(
self.textframe,
height=8,
wrap="word",
yscrollcommand=self.scrollbar.set,
)
self.textbox.grid(row=0, column=0, sticky="nsew")
self.scrollbar.config(command=self.textbox.yview)
for tag, color in {
"combat": "red",
"treasure": "gold",
"lore": "purple",
"peaceful": "green",
"default": "black",
}.items():
self.textbox.tag_config(tag, foreground=color)
self.textbox.config(state="disabled")
root = create_root()
main_container = tk.Frame(root)
main_container.grid(row=0, column=0, sticky="nsew")
main_container.rowconfigure(0, weight=1)
main_container.columnconfigure(0, weight=1)
room_container = tk.Frame(main_container, background="dark gray")
room_container.grid(row=0, column=0, sticky="nsew")
logbox_frame = tk.Frame(root)
logbox_frame.grid(row=1, column=0, sticky="nsew")
logbox_frame.rowconfigure(0, weight=1)
logbox_frame.columnconfigure(0, weight=1)
logbox = Logbox(logbox_frame)
logbox.grid(row=0, column=0, sticky="nsew")
root.mainloop()
保留原结构时需要修改哪些地方?
如果其他代码依赖原来的第 5 行,可以继续使用该行号,不必改成示例中的第 0 行。对应的配置必须与控件实际所在行一致。
在原来的 logbox.__init__() 中调整这些设置:
# self.textframe 位于 self 的第 0 行、第 0 列。
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
# 只让文本框所在列接收额外宽度。
self.textframe.columnconfigure(0, weight=1)
self.textframe.columnconfigure(1, weight=0)
# 滚动条沿第 5 行的高度伸展。
self.scrollbar.grid(row=5, column=1, sticky="ns")
# 替换原来的 self.grid(...)。
self.grid(row=5, column=0, sticky="nsew")
再为外层 logbox_frame 配置承载该组件的行列:
logbox_frame.columnconfigure(0, weight=1)
logbox_frame.rowconfigure(5, weight=1)
logbox_frame.grid(row=1, column=0, sticky="nsew")
这里不需要 columnspan=2:文本框和滚动条占用的是 self.textframe 内部的两列,整个 logbox 不需要在父容器中也跨两列。
底部高度和滚动行为的注意事项
原代码将 root 的两行都设为 weight=1,两行会按相同权重参与额外高度的分配。如果希望底部保持约 8 行文本的请求高度,可以像完整示例一样,将底部所在行设为 weight=0。height=8 表示文本行数,实际像素高度还受字体、边框等因素影响。
如果希望底部也随窗口增高,可以保留底部行的正权重,并确保这条路径上的每一级容器都配置了行权重和 sticky="nsew"。
原来的滚动关联方式可以保留:yscrollcommand=self.scrollbar.set 用于更新滚动条位置,command=self.textbox.yview 用于通过滚动条控制文本视图。state="disabled" 会禁止编辑;需要通过代码追加日志时,先临时切换为 "normal",写入后再恢复。
tkraise() 调整控件的堆叠顺序,不负责分配行列空间,不能修复这里的宽度问题。
备注:内容仅供参考。
