简体   繁体   English

画布中的Tkinter窗口未填充其父对象的高度

[英]Tkinter window in canvas doesn't fill its parent in height

I would like to have my scrollbar in the bottom of the frame and my text widgets filling the whole frame above the scrollbar. 我想将滚动条放在框架的底部,并将文本小部件填充到滚动条上方的整个框架。 I found some solution about the width configuration here but when I try to replace width with height, it does not work correctly. 我在这里找到了一些有关宽度配置的解决方案,但是当我尝试将宽度替换为高度时,它无法正常工作。

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()

Step 1: Remove space above and below the scrollbar 步骤1:删除滚动条上方和下方的空间

The expand option determines how tkinter handles unallocated space. expand选项确定tkinter如何处理未分配的空间。 Extra space will be evenly allocated to all widgets where the value is 1 or True . 多余的空间将平均分配给值为1True所有小部件。 Because it's set to 1 for the scrollbar, it is given some of the extra space, causing the padding above and below the widget. 因为滚动条设置为1 ,所以它有一些额外的空间,导致小部件上方和下方的填充。

What you want instead is for all of the space to be allocated only to the canvas. 相反,您想要的是将所有空间仅分配给画布。 Do this by setting expand to zero on the scrollbar: 通过在滚动条上将expand设置为零来执行此操作:

myscrollbar.pack(fill=X, expand=0)

Step 2: call a function when the canvas changes size 步骤2:当画布更改大小时调用一个函数

The next problem is that you want the inner frame to grow when the canvas changes size, so you need to bind to the <Configure> event of the canvas. 下一个问题是,当画布更改大小时,您希望内部框架增大,因此需要绑定到画布的<Configure>事件。

def OnCanvasConfigure(self, event):
    <code to set the size of the inner frame>
...
self.canvas.bind("<Configure>", self.OnCanvasConfigure)

Step 3: let the canvas control the size of the inner frame 步骤3:让画布控制内部框架的大小

You can't just change the size of the inner frame in OnCanvasConfigure , because the default behavior of a frame is to shrink to fit its contents. 您不能只在OnCanvasConfigure更改内部框架的OnCanvasConfigure ,因为框架的默认行为是缩小以适合其内容。 In this case you want the contents to expand to fit the frame rather than the frame shrink to fit the contents. 在这种情况下,您希望内容扩展以适合框架,而不是框架收缩以适合内容。

There are a couple ways you can fix this. 有几种方法可以解决此问题。 You can turn geometry propagation off for the inner frame, which will prevent the inner widgets from changing the size of the frame. 您可以关闭内部框架的几何图形传播 ,这将防止内部小部件更改框架的大小。 Or, you can let the canvas force the size of the frame. 或者,您可以让画布强制框架的大小。

The second solution is the easiest. 第二种解决方案是最简单的。 All we have to do is use the height of the canvas for the frame height, and the sum of the widths of the inner text widgets for the frame width. 我们要做的就是使用画布的高度作为框架的高度,使用内部文本小部件的宽度之和作为框架的宽度。

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)

Step 4: fix the scrollbar 步骤4:修复滚动条

There's still one more problem to solve. 还有一个问题要解决。 If you resize the window you'll notice that tkinter will chop off the scrollbar if the window gets too small. 如果您调整窗口的大小,您会注意到如果窗口太小,tkinter将切断滚动条。 You can solve this by removing the ability to resize the window but your users will hate that. 您可以通过取消调整窗口大小的功能来解决此问题,但是您的用户会讨厌此。

A better solution is to cause the text widgets to shrink before the scrollbar is chopped off. 更好的解决方案是在切掉滚动条之前使文本窗口小部件收缩。 You control this by the order in which you call pack . 您可以通过调用pack的顺序来控制它。

When there isn't enough room to fit all of the widgets, tkinter will start reducing the size of widgets, starting with the last widget added to the window. 当没有足够的空间容纳所有小部件时,tkinter将开始减小小部件的大小,从最后添加到窗口的小部件开始。 In your code the scrollbar is the last widget, but if instead you make it the canvas, the scrollbar will remain untouched and the canvas will shrink instead (which in turn causes the frame to shrink, which causes the text widgets to shrink). 在您的代码中,滚动条是最后一个窗口小部件,但是,如果改为将其制作为画布,则滚动条将保持不变,而画布将缩小(这反过来会导致框架缩小,从而导致文本窗口小部件收缩)。

myscrollbar.pack(side="bottom", fill=X, expand=0)
self.canvas.pack(fill=BOTH, expand=1)

Changing pack layout to grid layout for self.canvas and myscrollbar makes it work. 将pack布局更改为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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM