在可滚动 canvas 内创建可调整大小的 Tkinter 框架
Create resizable Tkinter frame inside of scrollable canvas
我正在使用 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 框架,在该框架上放置一个工作 canvas 和滚动条,将一个新框架放入 canvas,然后 return 将框架放入其中canvas.
虽然滚动条在上面的代码中运行良好,但 return 嵌套的 Tkinter 框架不会调整大小以适应其父级 canvas 的高度和宽度。如果父 canvas 太大,它看起来像这样:
(蓝色区域为canvas,红色为canvas内框。)
为了解决这个问题,我使用网格手动将嵌套框架放置在 canvas 上(请参阅 return 语句之前的注释代码)。 canvas 中的框架开始自行调整大小,但滚动条停止工作。
有没有一种简单的方法可以让 canvas 中的框架在不破坏滚动条的情况下自行调整大小?
Is there a simple way to allow the frame inside the canvas to resize itself without breaking the scrollbar?
简单?我想这取决于您对简单性的定义。这是可能的,但它需要一些额外的代码行。
滚动条仅在您使用 create_window
将框架添加到 canvas 并且仅当您让框架足够大以容纳其所有子项时才起作用,然后相应地设置 canvas bbox。当 window 调整大小时,如果框架小于 canvas,则需要强制框架大于它想要的大小,但如果框架更大,则需要让框架成为其首选大小比 canvas.
解决方案看起来类似于以下示例,超出了我的想象。请注意标签的使用,以便轻松找到内部框架。您可以轻松地存储 create_window
返回的 id 并使用它。这也利用了 event
对象具有 canvas.
的 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"))
我正在使用 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 框架,在该框架上放置一个工作 canvas 和滚动条,将一个新框架放入 canvas,然后 return 将框架放入其中canvas.
虽然滚动条在上面的代码中运行良好,但 return 嵌套的 Tkinter 框架不会调整大小以适应其父级 canvas 的高度和宽度。如果父 canvas 太大,它看起来像这样:
(蓝色区域为canvas,红色为canvas内框。)
为了解决这个问题,我使用网格手动将嵌套框架放置在 canvas 上(请参阅 return 语句之前的注释代码)。 canvas 中的框架开始自行调整大小,但滚动条停止工作。
有没有一种简单的方法可以让 canvas 中的框架在不破坏滚动条的情况下自行调整大小?
Is there a simple way to allow the frame inside the canvas to resize itself without breaking the scrollbar?
简单?我想这取决于您对简单性的定义。这是可能的,但它需要一些额外的代码行。
滚动条仅在您使用 create_window
将框架添加到 canvas 并且仅当您让框架足够大以容纳其所有子项时才起作用,然后相应地设置 canvas bbox。当 window 调整大小时,如果框架小于 canvas,则需要强制框架大于它想要的大小,但如果框架更大,则需要让框架成为其首选大小比 canvas.
解决方案看起来类似于以下示例,超出了我的想象。请注意标签的使用,以便轻松找到内部框架。您可以轻松地存储 create_window
返回的 id 并使用它。这也利用了 event
对象具有 canvas.
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"))