Tkinter 网格动态布局

Posted

技术标签:

【中文标题】Tkinter 网格动态布局【英文标题】:Tkinter Grid Dynamic Layout 【发布时间】:2018-05-22 03:09:21 【问题描述】:

我想创建一个网格布局,用一个填充第一行的网格,直到它用完窗口中的空间,并将项目动态移动到下面的行(如文本换行)。随着窗口宽度的调整,网格也会调整以适应。不需要调整框的大小。我打算保持每个小盒子的大小,但改变布局放置每个盒子的位置。

我想通过测量框架的宽度可以实现此功能,如果(框数)*(每个框的宽度)超过宽度,则移至下一行。我只是想知道是否有一种我不理解的更好的内置方法。

如果以上是唯一的选择,那么更新它的最佳方法是什么?我是否必须在窗口调整大小或其他方面设置事件?看来我不应该重新设计布局管理器,这就是那种感觉。我只是想检查是否已经内置了类似的功能。网格似乎是一个强大的布局管理器,但我一直找不到那个选项。

下面的图片描述了我想要使用网格布局在单个框架上使用相同的一组 6 个框的行为。

窗口的宽度足以容纳所有 6 个框,因此它们都适合第 1 行。然后它们会随着窗口大小的变化而调整。

【问题讨论】:

这不是 tkinter 的选项。您必须完全按照您提到的去做:监控窗口大小并根据需要重新布局。不需要太多代码。 我应该如何监控窗口大小的变化?这样做有什么负面影响吗?如果不推荐(因为它不是内置的),我可以找到不同的解决方案,但如果这样做是相当标准的做法,我肯定可以解决 使用“”事件。 【参考方案1】:

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

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

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

【讨论】:

不错的解决方案。我实际上喜欢大小不均的小部件如何以最佳方式打包。 我真的很喜欢这个解决方案!非常优雅和简单。它绝对给了我想要的东西,并且由于避免了可怕的宽度测量,我将其标记为解决方案。此外,正如@Novel 所提到的,它同样可以处理大小不一的盒子(尽管不那么美观)。这是完美的先生,谢谢。【参考方案2】:

这是一个工作示例:

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

请注意,这会破坏网格管理器的行跨度和列跨度功能。

【讨论】:

我测试了代码,它确实有效并且是一个有效的解决方案。我赞成并可能在将来使用它,但 Bryan 在下面的回答是更好的解决方案,因为它避免了测量宽度并简单地让布局管理器完成他们的工作。不过感谢您的出色回答! 这里唯一的问题是每次调用 regrid 时都会颠倒帧顺序【参考方案3】:

这是 Bryan 答案的简化版本,没有课程和一些额外的 cmets,适用于任何困惑并试图将其快速实施到自己的项目中的人。

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

【讨论】:

以上是关于Tkinter 网格动态布局的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 CustomScrollView 功能布局子网格的动态宽度?

如何在网格上动态重新布局用户控件?

当项目具有动态高度时,如何根据设备方向创建动态颤动网格布局

动态灵活的网格布局

为啥添加到网格布局的动态按钮彼此之间有很大的距离?

如何仅以编程方式在android中添加动态网格布局?