Tkinter网格动态布局

7
我想创建一个网格布局,其中网格填充第一行直到窗口空间用尽,并将动态地将项目移动到下一行(类似于文本换行)。随着窗口宽度的调整,网格会自动调整大小以适应。不希望调整框的大小。我打算保持每个小框的大小,但更改布局放置每个框的位置。
我想象这种功能可以通过测量框架的宽度来实现,如果(框的数量)*(每个框的宽度)超过宽度,则移到下一行。我只是想知道是否有内置的更好方法,我还没有理解。
如果上述是唯一的选项,最佳更新方式是什么?我必须在窗口调整大小或其他事件上设置事件吗?似乎不应该重新设计布局管理器,那就是那样的感觉。我只是想检查是否已经内置了类似的功能。网格似乎是一个强大的布局管理器,但我还没有找到该选项。
以下图片描述了我想要使用网格布局在单个框架上使用相同的6个框的行为。
窗口足够宽以容纳所有6个框,因此它们都适合第1行。随后随着窗口大小的更改而进行调整。 在此输入图像描述

enter image description here enter image description here


1
那在tkinter中不是一个选项。你必须按照你提到的方式监视窗口大小并根据需要重新布局。这不需要太多的代码。 - Novel
我应该如何监控窗口大小的变化?这样做有什么负面影响吗?如果不建议这样做(因为它不是内置的),我可以找到其他解决方案,但如果这是相当标准的做法,我肯定可以想出办法。 - Tarm
1
使用"<Configure>"事件。 - Novel
3个回答

16

如果您计划强制每个方块具有统一的大小,则最简单的解决方案是使用文本小部件作为容器,因为它具有内置的换行功能。

这里是一个可工作的示例。单击“添加”按钮以添加其他框。调整窗口大小以查看它们在窗口增长和缩小时自动换行。

import Tkinter as tk
import random

class DynamicGrid(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.text = tk.Text(self, wrap="char", borderwidth=0, highlightthickness=0,
                            state="disabled")
        self.text.pack(fill="both", expand=True)
        self.boxes = []

    def add_box(self, color=None):
        bg = color if color else random.choice(("red", "orange", "green", "blue", "violet"))
        box = tk.Frame(self.text, bd=1, relief="sunken", background=bg,
                       width=100, height=100)
        self.boxes.append(box)
        self.text.configure(state="normal")
        self.text.window_create("end", window=box)
        self.text.configure(state="disabled")

class Example(object):
    def __init__(self):
        self.root = tk.Tk()
        self.dg = DynamicGrid(self.root, width=500, height=200)
        add_button  = tk.Button(self.root, text="Add", command=self.dg.add_box)

        add_button.pack()
        self.dg.pack(side="top", fill="both", expand=True)

        # add a few boxes to start
        for i in range(10):
            self.dg.add_box()

    def start(self):
        self.root.mainloop()

Example().start()

1
不错的解决方案。我实际上很喜欢不均匀大小的小部件如何最优地打包。 - Novel
我真的很喜欢这个解决方案!非常优雅简洁。它确实给了我想要的东西,并且由于避免了可怕的宽度测量,我将把它标记为解决方案。 此外,正如@Novel所提到的,它同样能够处理大小不均的框(虽然不太美观)。这太完美了,谢谢您。 - Tarm

6

下面是一个可行的例子:

import Tkinter as tk

class AutoGrid(tk.Frame):
    def __init__(self, master=None, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)
        self.columns = None
        self.bind('<Configure>', self.regrid)

    def regrid(self, event=None):
        width = self.winfo_width()
        slaves = self.grid_slaves()
        max_width = max(slave.winfo_width() for slave in slaves)
        cols = width // max_width
        if cols == self.columns: # if the column number has not changed, abort
            return
        for i, slave in enumerate(slaves):
            slave.grid_forget()
            slave.grid(row=i//cols, column=i%cols)
        self.columns = cols

class TestFrame(tk.Frame):
    def __init__(self, master=None, **kwargs):
        tk.Frame.__init__(self, master, bd=5, relief=tk.RAISED, **kwargs)

        tk.Label(self, text="name").pack(pady=10)
        tk.Label(self, text=" info ........ info ").pack(pady=10)
        tk.Label(self, text="data\n"*5).pack(pady=10)

def main():
    root = tk.Tk()
    frame = AutoGrid(root)
    frame.pack(fill=tk.BOTH, expand=True)

    TestFrame(frame).grid() # use normal grid parameters to set up initial layout
    TestFrame(frame).grid(column=1)
    TestFrame(frame).grid(column=2)
    TestFrame(frame).grid()
    TestFrame(frame).grid()
    TestFrame(frame).grid()
    root.mainloop()

if __name__ == '__main__':
    main()

请注意,这将破坏网格管理器的 rowspan 和 columnspan 功能。

1
我测试了代码,它绝对可行并且是一个有效的解决方案。我已经点赞并可能在未来使用它,但是由于避免测量宽度并让布局管理器完成其工作,下面的Bryan答案是更好的解决方案。非常感谢你提供这个好答案! - Tarm
这里唯一的问题是每次调用regrid时它都会反转帧的顺序。 - fils capo

3
这是Bryan的回答简化版,去掉了类和一些额外的注释,适用于想快速将其实现到自己项目中并有些困惑的人。
from tkinter import *
import tkinter as tk

#Create main window
root = tk.Tk()

#Create WidgetWrapper
widgetWrapper = tk.Text(root, wrap="char", borderwidth=0,highlightthickness=0,state="disabled", cursor="arrow") 
#state = "disabled" is to disable text from being input by user
#cursor = "arrow" is to ensure when user hovers, the "I" beam cursor (text cursor) is not displayed

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

def additem():
    item = Label(bd = 5, relief="solid", text="O", bg="red") #Create the actual widgets
    widgetWrapper.window_create("end", window=item) #Put it inside the widget wrapper (the text)

# add a few boxes to start
for i in range(10):
    additem()

#Not needed to implement in other code, just an add button
add_button  = tk.Button(root, text="Add", command=additem)
add_button.pack()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接