Я думаю, что это важный вопрос. Мне нужен был этот макет потока в 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
.
Я подумал, что было бы более естественным программировать вместо того, чтобы на самом деле дать виджетам метод .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
Text
в качестве текстового поля и вводить текст или удалять элементы с помощью Backspace и т. д., что, скорее всего, нежелательно для использования его в качестве диспетчера графического интерфейса. Вы можете отключить редактирование с помощьюtextwidget.config(state=tk.DISABLED)
. Тем не менее, пользователь может выделить текст/пустое место, а курсор выглядит как текстовый курсор. Эти две вещи, наконец, также могут быть исправлены с помощью следующего кода, который устанавливает цвет выбора фона на обычный цвет фона текстового поля и превращает курсор в стрелку.textwidget.config(selectbackground=textwidget.cget('background'), cursor='arrow')
07.03.2015