Like I said before, while the existing answer might work, it might be inefficient since you are destroying and creating new widgets each time there is a change. Instead of this, you could create a function that will check if there is a change and then if there is extra or less items, the changes will take place:
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()
The code is commented to make it understandable line by line. An important thing to note here is the items_frame
, which makes it possible to get all the dynamically created widgets directly without having the need to store them to a list manually.
The function fetch_changed_list
is the one that changes the list and returns it. If you don't want to repeat calculate
every 1000ms (which is a good idea not to repeat infinitely), you could call the calculate
function each time you change the list.
def change_list():
# Logic to change the list
...
calculate() # To make the changes
After calculating the time for function executions, I found this:
Widgets redrawn |
Time before (in seconds) |
Time after (in seconds) |
400 |
0.04200148582458496 |
0.024012088775634766 |
350 |
0.70701003074646 |
0.21500921249389648 |
210 |
0.4723021984100342 |
0.3189823627471924 |
700 |
0.32096409797668457 |
0.04197263717651367 |
Where "before" is when destroying and recreating and "after" is only performing when change is needed.