canvas 中的 Tkinter window 未填充其父项的高度
Tkinter window in canvas doesn't fill its parent in height
我想让我的滚动条位于框架的底部,我的文本小部件填充滚动条上方的整个框架。我找到了一些关于宽度配置的解决方案 但是当我尝试用高度替换宽度时,它无法正常工作。
from tkinter import *
from tkinter import ttk
class MainView(Frame):
def FrameHeight(self, event):
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height=canvas_height)
def OnFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
sensorsFrame = Frame(self)
sensorsFrame.grid(row=0, sticky="nsew")
sensorsFrame.grid_columnconfigure(0, weight=1)
sensorsFrame.grid_rowconfigure(0, weight=1)
self.canvas = Canvas(sensorsFrame)
self.sensorsStatsFrame = Frame(self.canvas)
self.canvas.grid_rowconfigure(0, weight=1)
self.sensorsStatsFrame.grid_rowconfigure(0, weight=1)
myscrollbar = Scrollbar(sensorsFrame,orient=HORIZONTAL,command=self.canvas.xview)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.pack(fill=BOTH, expand=1)
myscrollbar.pack(fill=X, expand=1)
test0 = Text(self.sensorsStatsFrame, state=DISABLED)
test1 = Text(self.sensorsStatsFrame, width=150)
test0.grid(column=0, row=0, sticky="nsew")
test1.grid(column=1, row=0, sticky="nsew")
self.canvas_frame = self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.sensorsStatsFrame.bind("<Configure>", self.OnFrameConfigure)
#When I try to use what i found
#self.canvas.bind('<Configure>', self.FrameHeight)
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()
将 self.canvas 和 myscrollbar 的包布局更改为网格布局使其工作。
from tkinter import *
from tkinter import ttk
class MainView(Frame):
def FrameHeight(self, event):
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height = canvas_height)
def OnFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
sensorsFrame = Frame(self)
sensorsFrame.grid(row=0, sticky="nsew")
sensorsFrame.grid_rowconfigure(0, weight=1)
sensorsFrame.grid_columnconfigure(0, weight=1)
self.canvas = Canvas(sensorsFrame, bg="blue")
self.sensorsStatsFrame = Frame(self.canvas)
self.canvas.grid_rowconfigure(0, weight=1)
self.sensorsStatsFrame.grid_rowconfigure(0, weight=1)
myscrollbar = Scrollbar(sensorsFrame,orient=HORIZONTAL,command=self.canvas.xview)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.grid(row=0, sticky="nsew")
myscrollbar.grid(row=1, sticky="nsew")
test0 = Text(self.sensorsStatsFrame, state=DISABLED, bg="red")
test1 = Text(self.sensorsStatsFrame, width=150)
test0.grid(column=0, row=0, sticky="nsew")
test1.grid(column=1, row=0, sticky="nsew")
self.canvas_frame = self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.sensorsStatsFrame.bind("<Configure>", self.OnFrameConfigure)
self.canvas.bind('<Configure>', self.FrameHeight)
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()
第 1 步:删除滚动条上方和下方的 space
expand
选项决定了 tkinter 如何处理未分配的 space。额外的 space 将平均分配给值为 1
或 True
的所有小部件。因为滚动条设置为 1
,它被赋予了一些额外的 space,导致小部件上方和下方的填充。
您想要的是将所有 space 仅分配给 canvas。通过将滚动条上的 expand
设置为零来执行此操作:
myscrollbar.pack(fill=X, expand=0)
第 2 步:当 canvas 改变大小时调用函数
下一个问题是你希望内框在canvas改变大小时增长,所以你需要绑定到canvas的<Configure>
事件。
def OnCanvasConfigure(self, event):
<code to set the size of the inner frame>
...
self.canvas.bind("<Configure>", self.OnCanvasConfigure)
第三步:让canvas控制内框的大小
您不能只更改 OnCanvasConfigure
中内部框架的大小,因为框架的默认行为是缩小以适合其内容。在这种情况下,您希望内容扩展以适合框架而不是框架缩小以适合内容。
有几种方法可以解决这个问题。您可以为内部框架关闭 geometry propagation,这将防止内部小部件更改框架的大小。或者,您可以让 canvas 强制调整框架的大小。
第二个解决方案是最简单的。我们所要做的就是使用 canvas 的高度作为框架高度,并将内部文本小部件的宽度之和作为框架宽度。
def OnCanvasConfigure(self, event):
width = 0
for child in self.sensorsStatsFrame.grid_slaves():
width += child.winfo_reqwidth()
self.canvas.itemconfigure(self.canvas_frame, width=width, height=event.height)
第 4 步:修复滚动条
还有一个问题要解决。如果您调整 window 的大小,您会注意到如果 window 变得太小,tkinter 将切断滚动条。您可以通过删除调整 window 大小的功能来解决此问题,但您的用户会讨厌它。
更好的解决方案是在滚动条被截断之前使文本小部件收缩。您可以通过调用 pack
的顺序来控制它。
当没有足够的空间容纳所有小部件时,tkinter 将开始减小小部件的大小,从添加到 window 的最后一个小部件开始。在您的代码中,滚动条是最后一个小部件,但是如果您将其设为 canvas,则滚动条将保持不变,而 canvas 将缩小(这反过来会导致框架缩小,从而导致要缩小的文本小部件)。
myscrollbar.pack(side="bottom", fill=X, expand=0)
self.canvas.pack(fill=BOTH, expand=1)
我想让我的滚动条位于框架的底部,我的文本小部件填充滚动条上方的整个框架。我找到了一些关于宽度配置的解决方案
from tkinter import *
from tkinter import ttk
class MainView(Frame):
def FrameHeight(self, event):
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height=canvas_height)
def OnFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
sensorsFrame = Frame(self)
sensorsFrame.grid(row=0, sticky="nsew")
sensorsFrame.grid_columnconfigure(0, weight=1)
sensorsFrame.grid_rowconfigure(0, weight=1)
self.canvas = Canvas(sensorsFrame)
self.sensorsStatsFrame = Frame(self.canvas)
self.canvas.grid_rowconfigure(0, weight=1)
self.sensorsStatsFrame.grid_rowconfigure(0, weight=1)
myscrollbar = Scrollbar(sensorsFrame,orient=HORIZONTAL,command=self.canvas.xview)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.pack(fill=BOTH, expand=1)
myscrollbar.pack(fill=X, expand=1)
test0 = Text(self.sensorsStatsFrame, state=DISABLED)
test1 = Text(self.sensorsStatsFrame, width=150)
test0.grid(column=0, row=0, sticky="nsew")
test1.grid(column=1, row=0, sticky="nsew")
self.canvas_frame = self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.sensorsStatsFrame.bind("<Configure>", self.OnFrameConfigure)
#When I try to use what i found
#self.canvas.bind('<Configure>', self.FrameHeight)
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()
将 self.canvas 和 myscrollbar 的包布局更改为网格布局使其工作。
from tkinter import *
from tkinter import ttk
class MainView(Frame):
def FrameHeight(self, event):
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height = canvas_height)
def OnFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
sensorsFrame = Frame(self)
sensorsFrame.grid(row=0, sticky="nsew")
sensorsFrame.grid_rowconfigure(0, weight=1)
sensorsFrame.grid_columnconfigure(0, weight=1)
self.canvas = Canvas(sensorsFrame, bg="blue")
self.sensorsStatsFrame = Frame(self.canvas)
self.canvas.grid_rowconfigure(0, weight=1)
self.sensorsStatsFrame.grid_rowconfigure(0, weight=1)
myscrollbar = Scrollbar(sensorsFrame,orient=HORIZONTAL,command=self.canvas.xview)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.grid(row=0, sticky="nsew")
myscrollbar.grid(row=1, sticky="nsew")
test0 = Text(self.sensorsStatsFrame, state=DISABLED, bg="red")
test1 = Text(self.sensorsStatsFrame, width=150)
test0.grid(column=0, row=0, sticky="nsew")
test1.grid(column=1, row=0, sticky="nsew")
self.canvas_frame = self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.sensorsStatsFrame.bind("<Configure>", self.OnFrameConfigure)
self.canvas.bind('<Configure>', self.FrameHeight)
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()
第 1 步:删除滚动条上方和下方的 space
expand
选项决定了 tkinter 如何处理未分配的 space。额外的 space 将平均分配给值为 1
或 True
的所有小部件。因为滚动条设置为 1
,它被赋予了一些额外的 space,导致小部件上方和下方的填充。
您想要的是将所有 space 仅分配给 canvas。通过将滚动条上的 expand
设置为零来执行此操作:
myscrollbar.pack(fill=X, expand=0)
第 2 步:当 canvas 改变大小时调用函数
下一个问题是你希望内框在canvas改变大小时增长,所以你需要绑定到canvas的<Configure>
事件。
def OnCanvasConfigure(self, event):
<code to set the size of the inner frame>
...
self.canvas.bind("<Configure>", self.OnCanvasConfigure)
第三步:让canvas控制内框的大小
您不能只更改 OnCanvasConfigure
中内部框架的大小,因为框架的默认行为是缩小以适合其内容。在这种情况下,您希望内容扩展以适合框架而不是框架缩小以适合内容。
有几种方法可以解决这个问题。您可以为内部框架关闭 geometry propagation,这将防止内部小部件更改框架的大小。或者,您可以让 canvas 强制调整框架的大小。
第二个解决方案是最简单的。我们所要做的就是使用 canvas 的高度作为框架高度,并将内部文本小部件的宽度之和作为框架宽度。
def OnCanvasConfigure(self, event):
width = 0
for child in self.sensorsStatsFrame.grid_slaves():
width += child.winfo_reqwidth()
self.canvas.itemconfigure(self.canvas_frame, width=width, height=event.height)
第 4 步:修复滚动条
还有一个问题要解决。如果您调整 window 的大小,您会注意到如果 window 变得太小,tkinter 将切断滚动条。您可以通过删除调整 window 大小的功能来解决此问题,但您的用户会讨厌它。
更好的解决方案是在滚动条被截断之前使文本小部件收缩。您可以通过调用 pack
的顺序来控制它。
当没有足够的空间容纳所有小部件时,tkinter 将开始减小小部件的大小,从添加到 window 的最后一个小部件开始。在您的代码中,滚动条是最后一个小部件,但是如果您将其设为 canvas,则滚动条将保持不变,而 canvas 将缩小(这反过来会导致框架缩小,从而导致要缩小的文本小部件)。
myscrollbar.pack(side="bottom", fill=X, expand=0)
self.canvas.pack(fill=BOTH, expand=1)