首页 > 解决方案 > 在可滚动画布内创建可调整大小的 Tkinter 框架

问题描述

我正在使用 Python 和 Tkinter 开发一个应用程序,该应用程序需要各种输入表单。每个输入表单都需要一个滚动条,以防表单的父级太短而无法显示所有内容,因此在 Google 的帮助下,这就是我目前拥有的:

import tkinter as tk


def get_vertically_scrollable_frame(parent_frame: tk.Frame or tk.Tk) -> tk.Frame:
    """
    :param parent_frame: The frame to place the canvas and scrollbar onto.
    :return: A scrollable tk.Frame object nested within the parent_frame object.
    """
    assert isinstance(parent_frame, tk.Frame) or isinstance(parent_frame, tk.Tk)

    # Create the canvas and scrollbar.
    canvas = tk.Canvas(master=parent_frame, bg="blue")
    scrollbar = tk.Scrollbar(master=parent_frame, orient=tk.VERTICAL, command=canvas.yview)

    # Let the canvas and scrollbar resize to fit the parent_frame object.
    parent_frame.rowconfigure(0, weight=1)
    parent_frame.columnconfigure(0, weight=1)
    canvas.grid(row=0, column=0, sticky='news')
    scrollbar.grid(row=0, column=1, sticky='nes')

    # Link the canvas and scrollbar together.
    canvas.configure(yscrollcommand=scrollbar.set)
    canvas.bind('<Configure>', lambda x: canvas.configure(scrollregion=canvas.bbox("all")))

    # Create the tk.Frame that is within the canvas.
    canvas_frame = tk.Frame(master=canvas, bg="red")
    canvas.create_window((0, 0), window=canvas_frame, anchor="nw")

    # TODO: Let the canvas_frame object resize to fit the parent canvas.
    canvas.columnconfigure(0, weight=1)
    canvas.rowconfigure(0, weight=1)
    # canvas_frame.grid(row=0, column=0, sticky="news")  # Resizes the frame, but breaks the scrollbar.

    return canvas_frame


if __name__ == "__main__":
    window = tk.Tk()
    window.rowconfigure(0, weight=1)
    window.columnconfigure(0, weight=1)

    parent_frame = tk.Frame(master=window)
    parent_frame.grid(row=0, column=0, sticky="news")

    scrollable_frame = get_vertically_scrollable_frame(parent_frame)

    # Add the widgets to the new frame.
    scrollable_frame.columnconfigure(0, weight=1)  # Resize everything horizontally.
    tk.Label(master=scrollable_frame, text="First name").grid(row=0, column=0, sticky="w")
    tk.Entry(master=scrollable_frame).grid(row=1, column=0, sticky="ew")
    tk.Label(master=scrollable_frame, text="").grid(row=2, column=0, sticky="w")
    tk.Label(master=scrollable_frame, text="Last name").grid(row=3, column=0, sticky="w")
    tk.Entry(master=scrollable_frame).grid(row=4, column=0, sticky="ew")
    tk.Label(master=scrollable_frame, text="").grid(row=5, column=0, sticky="w")
    tk.Label(master=scrollable_frame, text="Email").grid(row=6, column=0, sticky="w")
    tk.Entry(master=scrollable_frame).grid(row=7, column=0, sticky="ew")
    tk.Label(master=scrollable_frame, text="").grid(row=8, column=0, sticky="w")
    tk.Label(master=scrollable_frame, text="Favorite color").grid(row=9, column=0, sticky="w")
    tk.Entry(master=scrollable_frame).grid(row=10, column=0, sticky="ew")
    tk.Label(master=scrollable_frame, text="").grid(row=11, column=0, sticky="w")
    tk.Frame(master=scrollable_frame).grid(row=12, column=0, sticky="news")
    scrollable_frame.rowconfigure(12, weight=1)  # Vertically resize filler frame from last line.
    tk.Button(master=scrollable_frame, text="Clear").grid(row=13, column=0, sticky="ews")
    tk.Button(master=scrollable_frame, text="Submit").grid(row=14, column=0, sticky="ews")

    window.mainloop()

这个函数接受一个空的 Tkinter 框架,在该框架上放置一个工作画布和滚动条,在画布中放置一个新框架,然后返回画布内的框架。

虽然滚动条在上面的代码中可以正常工作,但返回的嵌套 Tkinter 框架不会调整大小以适应其父画布的高度和宽度。如果父画布太大,它看起来像这样:

工作滚动条 1

工作滚动条 2

(蓝色区域是画布,红色区域是画布内的框架。)

为了解决这个问题,我使用网格手动将嵌套框架放置在画布上(请参阅 return 语句之前的注释代码)。画布内的框架开始自行调整大小,但滚动条停止工作。

滚动条损坏 1

滚动条损坏 2

有没有一种简单的方法可以让画布内的框架在不破坏滚动条的情况下自行调整大小?

标签: pythontkintercanvasscrollbar

解决方案


有没有一种简单的方法可以让画布内的框架在不破坏滚动条的情况下自行调整大小?

简单的?我想这取决于您对简单性的定义。这是可能的,但它需要几行额外的代码。

滚动条仅在您将框架添加到画布时才起作用,create_window并且只有当您让框架达到容纳其所有子项所需的大小,然后相应地设置画布 bbox 时,滚动条才起作用。当窗口调整大小时,如果框架比画布小,您需要强制框架比它想要的大,但如果框架比画布大,您需要让框架成为它的首选尺寸。

解决方案看起来像下面的例子,我想不到。请注意使用标签可以轻松找到内部框架。您可以轻松地存储由返回的 idcreate_window并改用它。这也利用了event对象具有画布width和属性的事实。height

def get_vertically_scrollable_frame(parent_frame: tk.Frame or tk.Tk) -> tk.Frame:
    ...
    canvas.create_window((0, 0), window=canvas_frame, anchor="nw", tags=("canvas_frame",))
    canvas.bind('<Configure>', handle_resize)
    ...

def handle_resize(event):
    canvas = event.widget
    canvas_frame = canvas.nametowidget(canvas.itemcget("canvas_frame", "window"))
    min_width = canvas_frame.winfo_reqwidth()
    min_height = canvas_frame.winfo_reqheight()
    if min_width < event.width:
        canvas.itemconfigure("canvas_frame", width=event.width)
    if min_height < event.height:
        canvas.itemconfigure("canvas_frame", height=event.height)

    canvas.configure(scrollregion=canvas.bbox("all"))

推荐阅读