Скажем, у меня есть список виджетов, сгенерированных tkinter с использованием цикла (в данном случае это customtkinter, но, поскольку tkinter более известен, поэтому я думаю, что было бы лучше сделать пример с ним), каждый виджет лежит в один и тот же кадр с другим текстом метки. Вот пример кода:
x=0
self.scrollable_frame = customtkinter.CTkScrollableFrame(self, label_text = "CTkScrollableFrame")
self.scrollable_frame.grid(row=1, column=2, padx=(20, 0), pady=(20, 0), sticky = "nsew")
self.scrollable_frame.grid_columnconfigure(0, weight=1)
self.scrollable_frame_switches = []
for i in range(x,100):
switch = customtkinter.CTkSwitch(master=self.scrollable_frame, text=f"CTkSwitch {i}")
switch.grid(row=i, column=0, padx=10, pady=(0, 20))
self.scrollable_frame_switches.append(switch)
Мой вопрос в том, что если список, который помог сгенерировать эти виджеты, изменится (в данном случае это просто цикл в диапазоне от 0 до 100, может измениться текст виджетов, размер списка ...), что было бы лучшим способом для обновления в реальном времени содержимое окна tkinter?
Ps: я пытался найти свой ответ во многих местах, но на данный момент лучший ответ, который я могу придумать, — это обновить весь фрейм с той же сеткой, но с измененным содержимым списка, я приведу его ниже. Есть ли способ лучше этого? Спасибо
@BryanOakley Привет, Брайан, в этом случае длина списка меняется, иногда меняется и все его содержимое, поскольку я пытаюсь реализовать функции сортировки, добавления и удаления. Спасибо
Вам нужно будет создать функцию, которая вызывается каждый раз при изменении списка. И внутри этого вы можете использовать управляющую переменную tkinter для обновления значения
Привет @DelriusEuphoria. Упомянутая вами управляющая переменная tkinter является конфигурацией, которая изменит значение для каждого из них?
Это StringVar
, IntVar
и так далее
насколько я понимаю, разве это не изменит значение каждого виджета, а не увеличит или уменьшит его количество?
Что именно вы ищете. Если список становится до range(50)
, вы хотите, чтобы количество виджетов также уменьшилось? Как насчет текста виджета (как он изменится в зависимости от размера списка)?
@DelriusEuphoria, да, это именно то, что мне нужно, я пытаюсь найти способ изменить размер всего списка, который также повлияет на виджет, уменьшить их общее количество, и если я решу удалить один и добавьте, например, другой с другим именем, тогда он также изменится в соответствии с этим, в основном обновив этот кадр в реальном времени. Так, например, если это только диапазон до (50), то это будет CTkswitch1-50 и все. Я нашел один способ, и я просто хотел знать, есть ли лучший способ сделать это.
Хорошо, способ, которым вы это делаете, кажется интуитивно понятным, но было бы неэффективно каждый раз создавать и уничтожать эти виджеты. Возможно, вы можете придумать лучший подход, например, проверить разницу между существующим списком и новым обновленным списком, чтобы увидеть, есть ли элементы больше или меньше. Оттуда, если есть больше элементов, вы можете обновить текст существующего виджета, а затем сделать оставшиеся дополнительные виджеты обязательными. Если меньше, то можно было изменить текст виджетов и потом удалить лишние виджеты
Поэтому я решил, что если я хочу нажать кнопку, эта кнопка должна иметь возможность обновлять список. Следовательно, я привязываю несвязанные кнопки в виджете к этой функции:
def sidebar_button_event(self):
global x
x=10
self.scrollable_frame.destroy()
self.after(0,self.update())
Что затем вызовет функцию обновления, которая сохранит значение изменения, а функция обновления просто перезапишет сетку:
def update(self):
self.scrollable_frame = customtkinter.CTkScrollableFrame(self, label_text = "CTkScrollableFrame")
self.scrollable_frame.grid(row=1, column=2, padx=(20, 0), pady=(20, 0), sticky = "nsew")
self.scrollable_frame.grid_columnconfigure(0, weight=1)
self.scrollable_frame_switches = []
for i in range(x,100):
switch = customtkinter.CTkSwitch(master=self.scrollable_frame, text=f"CTkSwitch {i}")
switch.grid(row=i, column=0, padx=10, pady=(0, 20))
self.scrollable_frame_switches.append(switch)
Если вы не уничтожите старые виджеты, вы только что создали утечку памяти, из-за которой вы будете использовать все больше и больше памяти для всех новых виджетов.
о, спасибо за упоминание, сейчас добавлю
Диапазон задержки между каждым обновлением — это то, что я хотел улучшить больше всего :( поэтому добавление его просто увеличило задержку примерно на 1/3 больше
Кроме того, вероятно, будет гораздо эффективнее просто изменить существующие виджеты, чем уничтожать и создавать их заново.
Но в этом случае, когда все содержимое может измениться, не лучше ли было бы заменить фрейм? (имя CTkScrollableFrame все еще остается прежним, изменился только сгенерированный переключатель) Я застрял, пытаясь понять, как удалить все эти невидимые переключатель, поэтому я просто решил пойти с рамкой
Как я уже говорил ранее, хотя существующий ответ может работать, он может быть неэффективным, поскольку вы уничтожаете и создаете новые виджеты каждый раз, когда происходит изменение. Вместо этого вы можете создать функцию, которая будет проверять, есть ли изменение, а затем, если есть лишние или меньшие элементы, изменения произойдут:
from tkinter import *
import random
root = Tk()
def fetch_changed_list():
"""Function that will change the list and return the new list"""
MAX = random.randint(5, 15)
# Create a list with random text and return it
items = [f'Button {x+1}' for x in range(MAX)]
return items
def calculate():
global items
# Fetch the new list
new_items = fetch_changed_list()
# Store the length of the current list and the new list
cur_len, new_len = len(items), len(new_items)
# If the length of new list is more than current list then
if new_len > cur_len:
diff = new_len - cur_len
# Change text of existing widgets
for idx, wid in enumerate(items_frame.winfo_children()):
wid.config(text=new_items[idx])
# Make the rest of the widgets required
for i in range(diff):
Button(items_frame, text=new_items[cur_len+i]).pack()
# If the length of current list is more than new list then
elif new_len < cur_len:
extra = cur_len - new_len
# Change the text for the existing widgets
for idx in range(new_len):
wid = items_frame.winfo_children()[idx]
wid.config(text=new_items[idx])
# Get the extra widgets that need to be removed
extra_wids = [wid for wid in items_frame.winfo_children()
[-1:-extra-1:-1]] # The indexing is a way to pick the last 'n' items from a list
# Remove the extra widgets
for wid in extra_wids:
wid.destroy()
# Also can shorten the last 2 steps into a single line using
# [wid.destroy() for wid in items_frame.winfo_children()[-1:-extra-1:-1]]
items = new_items # Update the value of the main list to be the new list
root.after(1000, calculate) # Repeat the function every 1000ms
items = [f'Button {x+1}' for x in range(8)] # List that will keep mutating
items_frame = Frame(root) # A parent with only the dynamic widgets
items_frame.pack()
for item in items:
Button(items_frame, text=item).pack()
root.after(1000, calculate)
root.mainloop()
Код прокомментирован, чтобы сделать его понятным построчно. Здесь важно отметить items_frame
, который позволяет получать все динамически созданные виджеты напрямую, без необходимости сохранять их в списке вручную.
Функция fetch_changed_list
изменяет список и возвращает его. Если вы не хотите повторять calculate
каждые 1000 мс (это хорошая идея, чтобы не повторять бесконечно), вы можете вызывать функцию calculate
каждый раз, когда вы меняете список.
def change_list():
# Logic to change the list
...
calculate() # To make the changes
Подсчитав время выполнения функций, я обнаружил следующее:
Где «до» — это уничтожение и воссоздание, а «после» — это выполнение только тогда, когда необходимы изменения.
извините за поздний ответ, но это потрясающий ответ. Большое спасибо!
В целях предоставления ответа, можем ли мы предположить, что длина списка не изменится, или может быть, что некоторые виджеты нужно будет удалить или добавить?