Хобрук: Ваш путь к мастерству в программировании

Как создать макет Tiling / Flow в TkInter?

Я хочу заполнить свое окно, скажем, метками, и я хочу, чтобы они были обернуты, как только столбец будет больше размера текущего окна (или, скорее, родительского фрейма).

Я пытался использовать макет grid, но тогда мне приходилось самому вычислять размер содержимого каждой строки, чтобы знать, когда поместить следующий элемент в следующую строку.

Причина, по которой я спрашиваю, заключается в том, что я хочу создать какие-то мозаичные значки файлов.

Или спросил по-другому, есть ли что-то вроде Swing FlowLayout для TkInter?


Ответы:


1

Что я делаю, когда хочу что-то подобное, так это использую текстовый виджет для контейнера. Текстовый виджет может иметь встроенные виджеты, и они обтекаются так же, как текст. Пока ваши виджеты имеют одинаковую высоту, эффект довольно приятный.

Например (вырезано и вставлено из вопроса по просьбе автора):

textwidget = tk.Text(master)
textwidget.pack(side=tk.LEFT, fill=tk.BOTH)
for f in os.listdir('/tmp'):
    textwidget.window_create(tk.INSERT, window=tk.Label(textwidget, text=f))
25.10.2012
  • Большой! благодарю вас. пожалуйста, вставьте пример кода, который я предоставил в свой ответ, чтобы следующему человеку, который ищет это, не пришлось искать документы в текстовом виджете :) 26.10.2012
  • Пользователь по-прежнему может использовать Text в качестве текстового поля и вводить текст или удалять элементы с помощью Backspace и т. д., что, скорее всего, нежелательно для использования его в качестве диспетчера графического интерфейса. Вы можете отключить редактирование с помощью textwidget.config(state=tk.DISABLED). Тем не менее, пользователь может выделить текст/пустое место, а курсор выглядит как текстовый курсор. Эти две вещи, наконец, также могут быть исправлены с помощью следующего кода, который устанавливает цвет выбора фона на обычный цвет фона текстового поля и превращает курсор в стрелку. textwidget.config(selectbackground=textwidget.cget('background'), cursor='arrow') 07.03.2015
  • @ nitro2k01: возможно, более простое решение — просто добавить привязки клавиш, отключающие выбор. Кроме того, когда виджет отключен, курсора вставки не будет, поэтому менять его не нужно. 07.03.2015

  • 2

    Я думаю, что это важный вопрос. Мне нужен был этот макет потока в tkinter, но я был удивлен, что он недоступен. Я сделал два разных решения, используя менеджер компоновки сетки. Они оба работают одинаково, но реализованы по-разному.

    Я создал объект под названием 'FlowFrame', который наследуется от класса Frame. Другими словами, его можно вставить и использовать везде, где будет использоваться фрейм, но я дал ему 2 метода. addWidget(yourwidget) и destroyWidgets(). Это работает путем привязки события изменения размера кадра к функции, которая размещает виджеты в сетке.

    Note:

    Его нельзя использовать на корневом уровне. Поэтому при использовании на корневом уровне вы должны поместить фрейм на корневой уровень, а затем расширить его до родительского контейнера. Причина в том, что я связался с событием, которое срабатывает при изменении размера кадра. Во фрейме это событие конфигурации запускается изменением размера фрейма. Я считаю, что на корневом уровне событие настройки запускается изменением размера фрейма, но я думаю, что изменение размера и перемещение дочерних элементов под корнем также вызовет событие настройки, создавая бесконечные циклы. Таким образом, событие configure для фрейма просто отличается от корневого уровня.

    Here is the FlowFrame object:
    """
    1) Make sure the instance of the frame is made to expand
    into parent container for the FlowFrame to work correctly.
    
    2) Cannot use at root level.
    """   
    from tkinter import Frame
    
    class FlowFrame(Frame):
        def __init__(self, *args, **kwargs):
            super(FlowFrame, self).__init__(*args, **kwargs)
            self.widgets=[]
            self.bind("<Configure>", lambda event:self._reorganizeWidgets())
    
        def addWidget(self, widget, **kwargs):
            #get the names of all widgets and place in list
            self.widgetChildList=[]
            for child in self.children:
                self.widgetChildList.append(child)
    
            #add the new widget to the list
            self.widgetChildList.append(widget)
    
            #grid the widget with its keyword arguments
            widget.grid(kwargs)
    
        def destroyWidgets(self):
    
            #get the names of all widgets in the frame and place in list
            self.widgetChildList=[]
            for child in self.children:
                self.widgetChildList.append(child)
    
            #destroy the widgets    
            for  i in range(len(self.children)):
                self.children[self.widgetChildList[i]].destroy()
    
            #reset list to empty
            self.widgetChildList=[]
                
        def _reorganizeWidgets(self):
            #set list to empty
            self.widgetChildList=[]
    
            #make new list based on current children of frame/self
            for child in self.children:
                self.widgetChildList.append(child)
    
            #algorithm for flow/gridding children based on window width and widgets widths
            rowNumber=0
            columnNumber=0
            width=0
            i=0
            while i<len(self.children):
                width+=self.children[self.widgetChildList[i]].winfo_width()
                if i==0:
                    self.children[self.widgetChildList[i]].grid(row=rowNumber, column=columnNumber)
                elif width > self.winfo_width():
                    rowNumber=rowNumber+1             
                    columnNumber = 0               
                    width=self.children[self.widgetChildList[i]].winfo_width()
                else:
                    columnNumber=columnNumber+1
                self.children[self.widgetChildList[i]].grid(row=rowNumber, column=columnNumber)
                i+=1
    
    And an example of its use:
    from tkinter import *
    from flowframe import FlowFrame
    
    def _destroyWidgets():
        myframe.destroyWidgets()
    
    def main():
    
        root = Tk()
    
        root.title("Using Inherited \"FlowFrame\"")
    
        global myframe
        myframe=FlowFrame(root)
        #make sure frame expands to fill parent window
        myframe.pack(fill="both", expand=1) 
    
        button=Button(myframe, text="---Button---")
        myframe.addWidget(button, sticky=NSEW)
    
        textbox=Text(myframe,width=3,height=2)
        myframe.addWidget(textbox)
    
        label=Label(myframe,text="Label")
        myframe.addWidget(label,sticky=NSEW)
    
        entry=Entry(myframe)
        myframe.addWidget(entry, sticky=NSEW)
    
        radioButton=Radiobutton(myframe,text="radio button")
        myframe.addWidget(radioButton)
    
        checkButton=Checkbutton(myframe,text="CheckButton")
        myframe.addWidget(checkButton)
    
        scale_widget = Scale(myframe, from_=0, to=100, orient=HORIZONTAL)
        myframe.addWidget(scale_widget)
    
        button2=Button(myframe, text="----Remove All---", command=_destroyWidgets)
        myframe.addWidget(button2)
    
        root.mainloop()
    
    main()
    

    Я сделал это доступным в виде пакета, поэтому, если вам понадобится использовать этот объект, вы также можете установить его с помощью команды pip install flowframe.


    Here is another project, where I added a .flow geometry manager and made them available to tkinter widgets.

    Я подумал, что было бы более естественным программировать вместо того, чтобы на самом деле дать виджетам метод .flow. Другими словами, я думаю, что было бы предпочтительнее иметь менеджер геометрии .flow. (у tkinter есть .pack, .place, .grid, и я добавил .flow.) Итак, вот еще один возможный ответ.

    Поэтому я сделал дочерними элементы каждого из виджетов (кроме виджета меню), и они унаследовали все свойства родителя. Затем я добавил метод к каждому виджету .flow(). Затем я переназначил имя родителя ребенку. Для этого вы должны сначала импортировать tkinter, а затем импортировать tkinterflow.

    from tkinter import *
    from tkinterflow import *
    

    А вот копия файла tkinterflow.py, который добавляет дополнительные методы:

    from tkinter import Button, Canvas, Checkbutton, Entry, Frame, Label, Listbox, \
        Menubutton, Message, Radiobutton, Scale, Scrollbar, Text, Spinbox, \
        LabelFrame, PanedWindow
    
    
    def _reorganizeWidgets(self):
        names = []
        for name in self.master.children:
            names.append(name)
        rowNumber = 0
        columnNumber = 0
        width = 0
        i = 0
        while i < len(self.master.children):
            width += self.master.children[names[i]].winfo_width()
            if i == 0:
                self.master.children[names[i]].grid(
                    row=rowNumber, column=columnNumber)
            elif width > self.master.winfo_width():
                rowNumber = rowNumber+1
                columnNumber = 0
                width = self.master.children[names[i]].winfo_width()
            else:
                columnNumber = columnNumber+1
            self.master.children[names[i]].grid(row=rowNumber, column=columnNumber)
            i += 1
    
    
    def _errorMessage():
        print("cannot be used at root level.  Put frame in root level and use frame")
    
    
    def _flow(self, cnf={}, **kw):
        if (str(self.master) == "."):
            _errorMessage()
            return
        self.tk.call(
            ('grid', 'configure', self._w)
            + self._options(cnf, kw))
        self.master.bind("<Configure>", lambda event: _reorganizeWidgets(self))
    
    
    class FlowButton(Button):
        def __init__(self, *args, **kwargs):
            super(FlowButton, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowCanvas(Canvas):
        def __init__(self, *args, **kwargs):
            super(FlowCanvas, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowCheckbutton(Checkbutton):
        def __init__(self, *args, **kwargs):
            super(FlowCheckbutton, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowEntry(Entry):
        def __init__(self, *args, **kwargs):
            super(FlowEntry, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowFrame(Frame):
        def __init__(self, *args, **kwargs):
            super(FlowFrame, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowLabel(Label):
        def __init__(self, *args, **kwargs):
            super(FlowLabel, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowListbox(Listbox):
        def __init__(self, *args, **kwargs):
            super(FlowListbox, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowMenubutton(Menubutton):
        def __init__(self, *args, **kwargs):
            super(FlowMenubutton, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowMessage(Message):
        def __init__(self, *args, **kwargs):
            super(FlowMessage, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowRadiobutton(Radiobutton):
        def __init__(self, *args, **kwargs):
            super(FlowRadiobutton, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowScale(Scale):
        def __init__(self, *args, **kwargs):
            super(FlowScale, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowScrollbar(Scrollbar):
        def __init__(self, *args, **kwargs):
            super(FlowScrollbar, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowText(Text):
        def __init__(self, *args, **kwargs):
            super(FlowText, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowSpinbox(Spinbox):
        def __init__(self, *args, **kwargs):
            super(Spinbox, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowLabelFrame(LabelFrame):
        def __init__(self, *args, **kwargs):
            super(FlowLabelFrame, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    class FlowPanedWindow(PanedWindow):
        def __init__(self, *args, **kwargs):
            super(FlowPanedWindow, self).__init__(*args, **kwargs)
    
        def flow(self, cnf={}, **kw):
            _flow(self, cnf={}, **kw)
    
    
    Button = FlowButton
    Canvas = FlowCanvas
    Checkbutton = FlowCheckbutton
    Entry = FlowEntry
    Frame = FlowFrame
    Label = FlowLabel
    Listbox = FlowListbox
    Menubutton = FlowMenubutton
    Message = FlowMessage
    Radiobutton = FlowRadiobutton
    Scale = FlowScale
    Scrollbar = FlowScrollbar
    Text = FlowText
    Spinbox = FlowSpinbox
    LabelFrame = FlowLabelFrame
    PanedWindow = FlowPanedWindow
    

    Поэтому я сделал это доступным в виде пакета, и вы можете установить его с помощью pip install tkinterflow.

    Затем, чтобы использовать его, используйте следующие операторы импорта, в этом порядке:

    from tkinter import *
    from tkinterflow import *
    

    Поэтому порядок этих операторов импорта очень важен.

    Вот пример его использования:

    from tkinter import *
    from tkinterflow import *       # ! Very important, put this right after import of tkinter functions
    
    root = Tk()              
    myFrame = Frame(root)                 # Very Important!, you cannot use .flow() methods in root
    myFrame.pack(fill=BOTH, expand=True)  # Very Important!, frame must stick to parent container walls
    
    button1 = Button(myFrame, text="--Button1--")
    button1.flow(sticky=NSEW)
    
    button2 = Button(myFrame, text="--Button2--")
    button2.flow(sticky=NSEW)
    
    button3 = Button(myFrame, text="--Button3--")
    button3.flow(sticky=NSEW)
    
    button4 = Button(myFrame, text="--Button4--")
    button4.flow(sticky=NSEW)
    
    button5 = Button(myFrame, text="--Button5--")
    button5.flow(sticky=NSEW)
    
    button6 = Button(myFrame, text="--Button6--")
    button6.flow(sticky=NSEW)
    
    button7 = Button(myFrame, text="--Button7--")
    button7.flow(sticky=NSEW)
    
    root.mainloop()
    
    So in summary, the question was:

    Или спросить по-другому, есть ли что-то вроде Swing FlowLayout для TkInter?

    The answer, I think is No, so I tried to make some things 'like Swing's FlowLayout':
    • FlowFrame, фрейм, в котором перемещаются виджеты.
    • или я дал виджетам метод .flow(), доступный в модуле tkinterflow, или в файле выше.

    Я думаю, что FlowFrame является лучшим решением в том смысле, что он не зависит от порядка оператора импорта, и некоторым людям может не понравиться присвоение имени родителя дочернему элементу. С другой стороны, использование метода .flow() кажется мне естественным после частого использования .pack() и .grid(). Если вам не нравится переназначение имени, вы можете отказаться от переназначения имен, и вы останетесь с дочерними элементами, например, виджетом FlowButton, виджетом FlowCheckbox и так далее.

    На сайтах pypi.org есть еще примеры:

    26.07.2021
    Новые материалы

    Введение в контекст React
    В этом посте мы поговорим о Context API, который был представлен в React 16, и о том, как вы можете их использовать. Что такое контекст? Глядя на определение из react docs , оно..

    Шлюз с лицензией OSS, совместимый с Apollo Federation v2, появится в WunderGraph
    Сегодня мы рады сообщить, что мы сотрудничаем с поддерживаемой YC Tailor Technologies, Inc. для внедрения Apollo Federation v2. Реализация будет лицензирована MIT (Engine) и Apache 2.0..

    Это оно
    Ну, я официально уволился с работы! На этой неделе я буду лихорадочно выполнять последние требования Думающего , чтобы я мог сосредоточиться на поиске работы. Что именно это значит?..

    7 полезных библиотек JavaScript, которые вы должны использовать в своем следующем проекте
    Усильте свою разработку JavaScript Есть поговорка «Не нужно изобретать велосипед». Библиотеки — лучший тому пример. Это поможет вам написать сложные и трудоемкие функции простым способом...

    Базовое руководство по переносу концепций обучения в глубокое обучение
    Обзор По мере того, как машинное обучение становится все более мощным и продвинутым, модели, обеспечивающие эту расширенную возможность, становятся все больше и начинают требовать огромного..

    C в C.R.U.D с использованием React-Redux
    Если вы использовали React, возможно, вы знакомы с головной болью, связанной с обратным потоком данных. Передача состояния реквизитам от родительских компонентов к дочерним компонентам может..

    5 обязательных элементов современного инструмента конвейера данных
    В цифровом мире предприятия используют конвейеры данных для перемещения, преобразования и хранения огромных объемов данных. Эти конвейеры составляют основу бизнес-аналитики и играют..