滚动条小部件在网格中布局时不起作用

Scrollbar widgets won't work when laid out in a grid

在 tkinter 库中使用 Python3 时,我多次遇到需要可滚动框架(具有垂直和水平滚动功能)以包含一组相关小部件的情况。由于滚动条不容易附加到框架(我听说你可以将滚动条附加到 canvases,但不能附加到框架),我决定创建我自己的 ScrolledFrame class——一个使用带有 class 的滚动条=51=] 内部显示框架的小部件。

在大多数情况下,它运行良好。我用 .pack() 方法布置了滚动条和 canvas。但是因为它们是 pack()ed,垂直小部件一直向下到屏幕底部,而不是留下空白 space。 (运行我的代码看我的意思。)

#!/bin/env python3
# File:  scrolledframe.py
# Written by:  J-L


import tkinter as tk


class ScrolledFrame(tk.Frame):
    def __init__(self, parent, **kwargs):
        # Create the "scaffolding" widgets:
        self.outerFrame = outerFrame = tk.Frame(parent, **kwargs)
        self.canvas = canvas = tk.Canvas(outerFrame)
        self.vscroll = vscroll = tk.Scrollbar(outerFrame, orient='vertical')
        self.hscroll = hscroll = tk.Scrollbar(outerFrame, orient='horizontal')

        # Pack/grid the vscroll, hscroll, and canvas widgets into their frame:
        usePack = True
        if usePack:  # use .pack()
            vscroll.pack(side='right', fill='y')
            hscroll.pack(side='bottom', fill='x')
            canvas.pack(fill='both', expand=True)
        else:  # use .grid()
            canvas.grid(row=0, column=0, sticky='nsew')
            vscroll.grid(row=0, column=1, sticky='ns')
            hscroll.grid(row=1, column=0, sticky='ew')

        # Hook up the commands for vscroll, hscroll, and canvas widgets:
        vscroll.configure(command=canvas.yview)
        hscroll.configure(command=canvas.xview)
        canvas.configure(yscrollcommand=vscroll.set,
                         xscrollcommand=hscroll.set)

        # Now create this very obejct (the innerFrame) as part of the canvas:
        super().__init__(canvas)
        innerFrame = self
        canvas.create_window((0, 0), window=innerFrame)
        innerFrame.bind('<Configure>',
            lambda event: canvas.configure(scrollregion=canvas.bbox('all'),
                                           width=event.width,
                                           height=event.height))


    # Accessor methods to access the four widgets involved:
    def outer_frame(self):  return self.outerFrame
    def vscroll(self):  return self.vscroll
    def hscroll(self):  return self.hscroll
    def inner_frame(self):  return self  # (included for completeness)


    # When .pack(), .grid(), or .place() is called on this object,
    # it should be invoked on the outerFrame, attaching that to its
    # parent widget:
    def pack(self, **kwargs):  self.outerFrame.pack(**kwargs)
    def grid(self, **kwargs):  self.outerFrame.grid(**kwargs)
    def place(self, **kwargs):  self.outerFrame.place(**kwargs)


def doExample():
    # Create the main window:
    root = tk.Tk()
    root.title('ScrolledFrame Example')

    # Create the scrolledFrame and a quit button:
    scrolledFrame = ScrolledFrame(root)
    scrolledFrame.pack()
    tk.Button(root, text='Quit', command=root.quit).pack(side='bottom')

    # Create some labels to display inside the scrolledFrame:
    for i in range(1, 30+1):
        tk.Label(scrolledFrame,
                 text='This is the text inside label #{}.'.format(i)
                ).grid(row=i-1, column=0)

    # Start the GUI:
    root.mainloop()


if __name__ == '__main__':
    doExample()

老实说,这不是什么大问题,但我后来考虑使用 .grid() 布局方法,将每个滚动条放在自己的行和自己的列中。

在我的代码的第 18 行附近,我包含了这一行:

usePack = True

如果您将其从 True 更改为 False,滚动条和 canvas 小部件将使用 .grid() 而不是 .pack() 进行布局,然后你就可以明白我在说什么了。

所以当我使用.grid()布局滚动条时,垂直滚动条下方的space确实如我所料出现,但现在滚动条的none工作!

这对我来说似乎很奇怪,因为我不明白为什么仅仅改变小部件的布局管理就会使它们的行为有任何不同。

问题 1:我做错了什么导致滚动条在 .grid() 布局时无法工作?

此外,我注意到,对于 .pack().grid(),"Quit" 按钮将在我调整 window 后立即移出 window =58=] 比开始时的要短。

问题 2: 有什么方法可以强制 "Quit" 按钮停留在 window 上(当 window 是调整大小),以我的 ScrolledFrame 为代价?

在此先感谢您的帮助!

What am I doing wrong that prevents the scrollbars from working when they are laid-out with .grid()?

问题是您没有告诉 grid 如何处理多余的 space,以及当没有足够的 space 时该怎么办。因此,小部件正好占用了它们所需的 space 数量。

使用 pack 你告诉它用 fillexpand 选项填充所有分配的 space。对于 grid,您需要为 canvas 所在的行和列赋予非零权重。

添加这两行,grid 的行为与 pack:

的行为相同
outerFrame.grid_rowconfigure(0, weight=1)
outerFrame.grid_columnconfigure(0, weight=1)

您可能还想在打包 scrolledFrame 时使用 fillexpand 选项,假设您希望它完全填充 window。

scrolledFrame.pack(fill="both", expand=True)

Is there a way I can force the "Quit" button to stay on the window (when the window is resizing), at the expense of my ScrolledFrame?

在调用 pack 其他小部件之前对其调用 pack。当没有足够的空间容纳所有小部件并且 tkinter 只需减小一个或多个小部件的大小以使其适合时,它首先减小添加的最后一个小部件的大小。