Tkinter滚动条网格布局下Canvas设置宽高不生效问题问询
问题描述
我尝试在Tkinter的Canvas组件中搭配使用滚动条,目前已通过scrollregion参数配置实现了滚动条正常展示和滚动功能,但无法使窗口保持正确的设定尺寸。
我期望窗口宽为500、高为300,但实际弹出的窗口尺寸会延展到内部Frame和Label组件的总大小,并没有沿用Canvas初始化时设置的宽高参数。
复现代码
self.windows["project_management"] = tk.Toplevel(self) self.windows["project_management"].canvas = { "main": tk.Canvas(self.windows["project_management"], width = 500, height = 300,scrollregion = (0, 0, 1000, 1000)) } # WORKBENCH self.windows["project_management"].canvas["main"].scrollbar_y = tk.Scrollbar(self.windows["project_management"], orient = tk.VERTICAL, command = self.windows["project_management"].canvas["main"].yview) self.windows["project_management"].canvas["main"].scrollbar_y.grid(row = 0, column = 0, sticky = tk.N + tk.S) self.windows["project_management"].canvas["main"]["yscrollcommand"] = self.windows["project_management"].canvas["main"].scrollbar_y.set # / WORKBENCH self.windows["project_management"].canvas_ordinate = [ self.windows["project_management"].canvas["main"], ] for i, x in enumerate(self.windows["project_management"].canvas_ordinate): x.grid(row = i, column = 1) directory = self.variables["main"]["project_directory_bin"].get() print("Directory: {directory}.".format(directory = directory)) path = pathlib.Path(directory) print("Directory Path: {path}.".format(path = path)) json_files = [] # WORKBENCH for i, x in enumerate(path.iterdir()): print("Path: {path}.".format(path = x)) xx = str(x).split(".") if xx[1] == "json": json_files.append(x) print(json_files) content = [] for i, x in enumerate(json_files): with open(str(x), "r") as file: json_file = json.load(file) content.append( { "complete_time_strftime": json_file["complete_time_strftime"], "full_duration": json_file["full_duration"], "initial_time_strftime": json_file["initial_time_strftime"], "task": json_file["task"], } ) # HEADER ROW self.windows["project_management"].canvas["main"].frames_ordinate = [ tk.Frame(self.windows["project_management"].canvas["main"]), ] self.windows["project_management"].canvas["main"].frames_ordinate[0].labels_ordinate = [ tk.Label(self.windows["project_management"].canvas["main"].frames_ordinate[0], text = "Initial Time" + " |"), tk.Label(self.windows["project_management"].canvas["main"].frames_ordinate[0], text = "Complete Time" + " |"), tk.Label(self.windows["project_management"].canvas["main"].frames_ordinate[0], text = "Objective" + " |"), tk.Label(self.windows["project_management"].canvas["main"].frames_ordinate[0], text = "Duration"), ] # / HEADER ROW for i, x in enumerate(content): self.windows["project_management"].canvas["main"].frames_ordinate.append(tk.Frame(self.windows["project_management"].canvas["main"])) self.windows["project_management"].canvas["main"].frames_ordinate[i + 1].labels_ordinate = [ tk.Label(self.windows["project_management"].canvas["main"].frames_ordinate[i + 1], text = content[i]["initial_time_strftime"] + " |"), tk.Label(self.windows["project_management"].canvas["main"].frames_ordinate[i + 1], text = content[i]["complete_time_strftime"] + " |"), tk.Label(self.windows["project_management"].canvas["main"].frames_ordinate[i + 1], text = content[i]["task"] + " |"), tk.Label(self.windows["project_management"].canvas["main"].frames_ordinate[i + 1], text = content[i]["full_duration"]), ] for i, x in enumerate(self.windows["project_management"].canvas["main"].frames_ordinate): # (i + 1) as header row exists x.grid(row = i, column = 0) for ii, xx in enumerate(self.windows["project_management"].canvas["main"].frames_ordinate[i].labels_ordinate): xx.grid(row = 0, column = ii)
窗口效果

解决方案
问题原因
直接将承载内容的Frame作为Canvas的子组件用grid布局,Tkinter的布局管理器默认会自动放大父组件尺寸适配子组件,Canvas初始化设置的宽高会被该逻辑覆盖,自然会连同窗口一起被内部的长列表撑大。同时内容没有挂载到Canvas的虚拟滚动画布上,滚动逻辑本身也不符合Canvas滚动容器的标准实现。
修复步骤
- 固定Toplevel窗口尺寸,禁止自动适配
在创建Toplevel后立即添加如下代码,直接指定窗口尺寸:
# 宽度520是预留了滚动条的宽度,可根据实际需求调整 self.windows["project_management"].geometry("520x300") # 禁止手动拖拽调整窗口大小,不需要可以删除 self.windows["project_management"].resizable(False, False)
- 调整内容容器的挂载方式
不要把所有行Frame直接放在Canvas里布局,先创建一个总内容容器Frame,所有行组件都放在这个总容器里,再用create_window方法将总容器挂载到Canvas的虚拟画布上:
# 创建总内容容器,所有行都放在这个容器里 content_frame = tk.Frame(self.windows["project_management"].canvas["main"]) # 将总容器挂载到Canvas的左上角 self.windows["project_management"].canvas["main"].create_window((0, 0), window=content_frame, anchor="nw") # 绑定内容尺寸变化事件,自动更新滚动区域 def update_scrollregion(event): self.windows["project_management"].canvas["main"].config(scrollregion=self.windows["project_management"].canvas["main"].bbox("all")) content_frame.bind("<Configure>", update_scrollregion)
- 修改原有行组件的父容器
将原来所有行Frame的父组件从Canvas改成上面创建的content_frame:
# HEADER ROW 修改父容器为content_frame self.windows["project_management"].canvas["main"].frames_ordinate = [ tk.Frame(content_frame), ] # 后续数据行的Frame父组件同样改成content_frame for i, x in enumerate(content): self.windows["project_management"].canvas["main"].frames_ordinate.append(tk.Frame(content_frame))
- 可选:添加布局权重,保证窗口缩放时Canvas可正常占满空间
self.windows["project_management"].grid_rowconfigure(0, weight=1) self.windows["project_management"].grid_columnconfigure(1, weight=1)
修改后窗口就会固定为设定的尺寸,内部内容超出后即可通过滚动条滑动查看,不会再被撑大。
内容的提问来源于stack exchange,提问作者tjarolimek
相关产品推荐
相关产品推荐

