tkinter grid布局下标签边框随内容抖动,不使用place和固定宽度如何解决
我正在使用tk/tkinter搭建包含大量不同尺寸frame的界面,这些frame中会放置按钮、标签、文本字段,运行过程中会频繁执行frame.pack()与frame.destroy()操作。我计划先为根frame配置6x4的grid网格作为底层布局,后续按需在网格上叠加临时frame。我的核心目标是让frame、标签首先根据用户设备的宽高比,按照我设置的行列weight完成尺寸适配,布局传播完成后就将所有边框位置锁定不再变动。
以下是问题的简化示例(颜色仅用于区分间距,便于调试):
import tkinter as tk import random class CharacterInterface: def __init__(self, master): self.display_text = [''] self.master = master self.master.attributes('-fullscreen', True) self.master.title("Interface Template") self.master.configure(bg='#000000') # generates a 6x4 grid to underpin the interface/keep things spaced out for i in range(6): for j in range(4): self.master.grid_columnconfigure(i, weight=1) self.master.grid_rowconfigure(j, weight=1) self.dummy_frame = tk.Frame(self.master, relief=tk.RIDGE, borderwidth=4, bg='#' + str(2 * j) + str(2 * j) + str(2 * j) + str(i) + str(i) + str(i)) self.dummy_label = tk.Label(master=self.dummy_frame, bg='#' + str(i) + str(i) + str(i) + str(2 * j) + str(2 * j) + str(2 * j)) self.dummy_frame.grid(row=j, column=i, sticky='nsew') self.dummy_label.pack(ipadx=8, ipady=8, fill="both", expand=True) self.ch_frame = tk.Frame(self.master, bg='#000077') self.ch_frame.grid(row=0, column=0, rowspan=2, columnspan=3, sticky="nsew") self.ch_frame.grid_propagate(False) # this locks ch_frame's right border self.ch_frame.grid_columnconfigure(0, weight=1) self.ch_frame.grid_columnconfigure(1, weight=3) self.ch_frame.grid_rowconfigure(0, weight=1) self.ch_frame.grid_rowconfigure(1, weight=6) self.chtop_label = tk.Label(master=self.ch_frame, relief=tk.RIDGE, borderwidth=4, fg="#FFFFFF", bg='#000000', font=('Courier', 12), justify="left", anchor='nw') self.chtop_label.grid(row=0, column=0, columnspan=2, sticky='nsew') self.chleft_label = tk.Label(master=self.ch_frame, relief=tk.RIDGE, borderwidth=4, fg="#FFFFFF", bg='#000000', font=('Courier', 12), justify="left", anchor='nw') self.chleft_label.grid(row=1, column=0, sticky='nsew') self.chright_label = tk.Label(master=self.ch_frame, relief=tk.RIDGE, borderwidth=4, fg="#FFFFFF", bg='#000000', font=('Courier', 12), justify="left", anchor='nw') self.chright_label.grid(row=1, column=1, sticky='nsew') self.test_button = tk.Button(self.chright_label, text="test", bg='#000000', fg="#FFFFFF", font=('Courier', 12), relief=tk.FLAT, anchor='w', command=lambda: self.toss()) self.test_button.pack(side='left') def toss(self): self.test_button.pack_forget() str_length, str_instance = random.randrange(1, 10), "" for i in range(str_length): str_instance = str_instance + 'a' self.chleft_label['text'] = str_instance self.test_button.pack(side='left') root = tk.Tk() my_gui = CharacterInterface(root) root.mainloop()
上述示例中已经生成了6x4网格,我通过self.ch_frame.grid_propagate(False)配置锁定了ch_frame(三个标签的父frame)的右侧边框,但点击按钮给chleft_label设置随机长度的字符串时,两个标签之间的中间边框会出现抖动位移。
有没有办法在不使用place()布局、也不手动指定宽度值的前提下锁定边框位置?我知道可以给chleft_label设置占位字符串实现,但我更希望通过propagate相关配置解决,而非使用这类临时方案,毕竟我需要管理大量这类frame。我也不想放弃ch_frame内部的grid布局,否则会丢失我需要的仅按需更新frame/标签的核心功能。
出现边框抖动的核心原因是:你仅关闭了外层ch_frame的布局传播,锁定了容器的总宽高,但ch_frame内部的grid布局默认会先按子组件的内容计算基础列宽,再将剩余空间按weight比例分配。当标签文本长度变化时,子组件的请求宽度会变化,进而导致两列的基础宽度分配改变,总宽固定的前提下两列的实际宽度就会偏移,出现边框抖动。
不需要替换布局、不需要手动设置固定宽度,仅需要给ch_frame内部的列配置添加uniform参数即可实现严格按weight比例分配宽度:
# 把原来的两行列配置修改为如下内容,同一个uniform组的列会严格按weight比例分配总宽度 self.ch_frame.grid_columnconfigure(0, weight=1, uniform="ch_col") self.ch_frame.grid_columnconfigure(1, weight=3, uniform="ch_col")
uniform参数的作用是将指定的行/列划入同一个统一尺寸组,同组内的所有行/列会完全忽略子组件的尺寸请求,严格按照weight比例分配容器的总可用空间,边框位置会完全固定不会再抖动。如果需要锁定行高比例,给行配置加uniform参数即可。
内容的提问来源于stack exchange,提问作者Nathan

