Tkinter, обновляйте виджеты в режиме реального времени, если список изменен

Скажем, у меня есть список виджетов, сгенерированных 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: я пытался найти свой ответ во многих местах, но на данный момент лучший ответ, который я могу придумать, — это обновить весь фрейм с той же сеткой, но с измененным содержимым списка, я приведу его ниже. Есть ли способ лучше этого? Спасибо

В целях предоставления ответа, можем ли мы предположить, что длина списка не изменится, или может быть, что некоторые виджеты нужно будет удалить или добавить?

Bryan Oakley 12.02.2023 20:45

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

V21 12.02.2023 20:47

Вам нужно будет создать функцию, которая вызывается каждый раз при изменении списка. И внутри этого вы можете использовать управляющую переменную tkinter для обновления значения

Delrius Euphoria 12.02.2023 21:27

Привет @DelriusEuphoria. Упомянутая вами управляющая переменная tkinter является конфигурацией, которая изменит значение для каждого из них?

V21 12.02.2023 21:34

Это StringVar, IntVar и так далее

Delrius Euphoria 12.02.2023 21:37

насколько я понимаю, разве это не изменит значение каждого виджета, а не увеличит или уменьшит его количество?

V21 12.02.2023 21:41

Что именно вы ищете. Если список становится до range(50), вы хотите, чтобы количество виджетов также уменьшилось? Как насчет текста виджета (как он изменится в зависимости от размера списка)?

Delrius Euphoria 12.02.2023 22:11

@DelriusEuphoria, да, это именно то, что мне нужно, я пытаюсь найти способ изменить размер всего списка, который также повлияет на виджет, уменьшить их общее количество, и если я решу удалить один и добавьте, например, другой с другим именем, тогда он также изменится в соответствии с этим, в основном обновив этот кадр в реальном времени. Так, например, если это только диапазон до (50), то это будет CTkswitch1-50 и все. Я нашел один способ, и я просто хотел знать, есть ли лучший способ сделать это.

V21 12.02.2023 23:11

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

Delrius Euphoria 12.02.2023 23:40
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
9
93
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

   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)

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

Bryan Oakley 12.02.2023 21:00

о, спасибо за упоминание, сейчас добавлю

V21 12.02.2023 21:02

Диапазон задержки между каждым обновлением — это то, что я хотел улучшить больше всего :( поэтому добавление его просто увеличило задержку примерно на 1/3 больше

V21 12.02.2023 21:04

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

Bryan Oakley 12.02.2023 21:04

Но в этом случае, когда все содержимое может измениться, не лучше ли было бы заменить фрейм? (имя CTkScrollableFrame все еще остается прежним, изменился только сгенерированный переключатель) Я застрял, пытаясь понять, как удалить все эти невидимые переключатель, поэтому я просто решил пойти с рамкой

V21 12.02.2023 21:05
Ответ принят как подходящий

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

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

Подсчитав время выполнения функций, я обнаружил следующее:

Виджеты перерисованы Время до (в секундах) Время после (в секундах) 400 0.04200148582458496 0,024012088775634766 350 0,70701003074646 0,21500921249389648 210 0,4723021984100342 0,3189823627471924 700 0,32096409797668457 0,04197263717651367

Где «до» — это уничтожение и воссоздание, а «после» — это выполнение только тогда, когда необходимы изменения.

извините за поздний ответ, но это потрясающий ответ. Большое спасибо!

V21 13.02.2023 19:32

Другие вопросы по теме