Question: I am writing mass spec data reduction software. I want to provide the user with a list of all samples in the current sequence, next to a check box to show whether the analysis data has been reduced (viewing the analysis reduces the data). The user should be able to click on a sample in the list to jump to it, or use buttons to go to the Next or Previous analysis. The sample label should appear like this when the analysis is un-reduced: ☐ CB-042  and when viewed/reduced: ☑ CB-042  This worked great when I was using tkinter: I could just destroy the entire list and repopulate it every time I wanted to update an entry, and there was basically no overhead/delay. I want my software to be more modern-looking, however, so I reached for CTkListbox instead. This looks way better, except that my previous approach of deleting and repopulating the entire list with every update resulted in the CTkListbox visually removing and repopulating the entire list every time. This takes about two seconds with a longer sequence, and is obviously undesirable behavior. My next idea was to delete the individual entry from the listbox, and re-add it with a check mark. This somehow resulted in program continuously deleting the first unchecked item in the list until the list was emptied of unchecked items and the program froze. This also generates a ton of errors about references to list items that no longer exist, so I guess deleting and re-entering the list item won't work either. Below is a not-so-minimal-but-still-reproducible example of where I'm currently at. The functions highlight_current_sample() and on_sample_select() are broken and need to be re-written, but I'm not sure how to do this correctly. How can I accomplish my goal of inserting a checked box for reduced samples in the list? I'd switch to themed tk treeview if that is easier. import tkinter as tk import customtkinter as ctk import matplotlib import matplotlib.pyplot as plt import matplotlib.backends.backend_tkagg as tkagg from tkinter import ttk from CTkListbox import * # highlights the current sample in the list def highlight_current_sample(sample_listbox, filtered_data): global analysis_index # replace the sample with a checked box if it is reduced if filtered_data[analysis_index].reduced and sample_listbox.get(analysis_index)[0] == '☐': sample_listbox.delete(analysis_index) # print('deleting sample', analysis_index) sample_listbox.insert(analysis_index, f'☑ {filtered_data[analysis_index].analysis_label}') sample_listbox.activate(analysis_index) # moves to the selected sample when clicked in the list def on_sample_select(event, sample_listbox, filtered_data): global analysis_index widget = event.widget # get the listbox widget try: index = int(widget.curselection()) analysis_index = index filtered_data[analysis_index].reduced = True update_buttons(filtered_data) highlight_current_sample(sample_listbox, filtered_data) except IndexError: pass # ignore clicks outside of items def on_next(filtered_data, sample_listbox): global analysis_index filtered_data[analysis_index].reduced = True analysis_index = (analysis_index + 1) % len(filtered_data) update_buttons(filtered_data) highlight_current_sample(sample_listbox, filtered_data) def on_previous(filtered_data, sample_listbox): global analysis_index analysis_index = (analysis_index - 1) % len(filtered_data) update_buttons(filtered_data) highlight_current_sample(sample_listbox, filtered_data) def update_buttons(filtered_data): global analysis_index # disable previous button on first index if analysis_index == 0: prev_button.config(state=tk.DISABLED) else: prev_button.config(state=tk.NORMAL) # convert next to finish on last index if analysis_index == len(filtered_data) - 1: view_button.pack(side=tk.BOTTOM, fill=tk.X, expand=True) # Show the finish button next_button.pack_forget() # Hide the next button else: next_button.pack(side=tk.BOTTOM, fill=tk.X, expand=True) # Show the next button view_button.pack_forget() # Hide the finish button class Analysis: def __init__(self, analysis_label, reduced): self.analysis_label = analysis_label self.reduced = reduced # Create a list of Analysis objects filtered_data = [ Analysis("Analysis 1", False), Analysis("Analysis 2", False), Analysis("Analysis 3", False), Analysis("Analysis 4", False), Analysis("Analysis 5", False), Analysis("Analysis 6", False), Analysis("Analysis 7", False), Analysis("Analysis 8", False), Analysis("Analysis 9", False), Analysis("Analysis 10", False), Analysis("Analysis 11", False), Analysis("Analysis 12", False) ] # force matplotlib backend TkAgg (for MacOSX development) matplotlib.use('TkAgg') # set the appearance mode to light ctk.set_appearance_mode('light') # initiate GUI window = tk.Tk() # define and pack main frame main_frame = ctk.CTkFrame(window) main_frame.pack(fill=tk.BOTH, expand=True) # left frame (to hold stats and buttons) left_frame = ctk.CTkFrame(main_frame) left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # define and pack frame for buttons button_frame = ctk.CTkFrame(left_frame) button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=(0, 1), padx=(2, 0)) # define and pack center frame for raw data plots and sample list center_frame = ctk.CTkFrame(main_frame) center_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) # initialize the data frame, figure and canvas data_frame = ctk.CTkFrame(center_frame) figure = plt.figure(figsize=(15,8)) canvas = tkagg.FigureCanvasTkAgg(figure, master=data_frame) # initialize the right frame, stats frame, and sample_listbox # set light_color and dark_color as very light gray and very dark gray respectively stats_frame = ctk.CTkFrame(left_frame, border_color='gray95') right_frame = ctk.CTkFrame(center_frame) # sample list frame sample_listbox = CTkListbox( right_frame, font=('TkDefaultFont', 11), width=220, label_anchor='w' ) # pack the sample listbox for analysis in filtered_data: if analysis.reduced: sample_listbox.insert(tk.END, f'☑ {analysis.analysis_label}') else: sample_listbox.insert(tk.END, f'☐ {analysis.analysis_label}') sample_listbox.pack(fill=tk.BOTH, expand=True) # initialize and pack buttons global prev_button, exit_button, next_button, view_button s = ttk.Style() s.configure('TButton', font=('TkDefaultFont', 16), anchor='center', justify='center') s.configure("TButton.label", font=('TkDefaultFont', 16), justify='center', anchor="center") exit_button = ttk.Button(button_frame, text="Exit", command=lambda: window.quit(), style='TButton', ) prev_button = ttk.Button(button_frame, text="Previous", command=lambda: on_previous(filtered_data, sample_listbox), style='TButton', ) next_button = ttk.Button(button_frame, text="Next", command=lambda: on_next(filtered_data, sample_listbox), style='TButton', ) view_button = ttk.Button(button_frame, text="View\nAll\nAnalyses", command=window.quit, style='TButton', ) # Place the buttons in their respective rows exit_button.pack(side=tk.BOTTOM, fill=tk.X, expand=True, ipady=20, ipadx=10) prev_button.pack(side=tk.BOTTOM, fill=tk.X, expand=True, ipady=20, ipadx=10) view_button.pack(side=tk.BOTTOM, fill=tk.X, expand=True, ipady=20, ipadx=10) next_button.pack(side=tk.BOTTOM, fill=tk.X, expand=True, ipady=20, ipadx=10) # pack the stats frame on the left stats_frame.pack(side=tk.TOP, fill=tk.X, expand=True, padx=(3,0)) # pack frame for data data_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0,2)) # draw the canvas and pack it canvas.draw() canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) # sample list packing right_frame.pack(side=tk.LEFT, fill=tk.Y) # header label for the sample list header_label = ctk.CTkLabel(right_frame, text='Analyses', font=('TkDefaultFont', 20, 'bold')) header_label.pack(pady=(10,10)) # set current_plot_index global analysis_index analysis_index = 0 # bind the listbox to the sample select function sample_listbox.bind("<<ListboxSelect>>", lambda event: on_sample_select(event, sample_listbox, filtered_data) ) # update the panes update_buttons(filtered_data) highlight_current_sample(sample_listbox, filtered_data) # main window loop window.mainloop() 

Answer: The solution was to only use insert, not delete. I also needed to unbind and rebind the listbox with every change. It also gave buggy behavior while using globals so I switched to passing the variables properly. Here's the updated code: # highlights the current analysis in the list def highlight_current_analysis(analysis_listbox, analysis_index, filtered_data): # check that the analysis is reduced and that the symbol is ☐ before changing to ☑ if filtered_data[analysis_index.get()].reduced and analysis_listbox.get(analysis_index.get())[0] == '☐': analysis_listbox.insert(analysis_index.get(), f'☑ {filtered_data[analysis_index.get()].analysis_label}') analysis_listbox.activate(analysis_index.get()) # moves to the selected analysis when clicked in the list def on_analysis_select(analysis_index, analysis_listbox, filtered_data, fit_type, baseline_type, figure, canvas, stats_frame, prev_button, next_button, view_button): analysis_index.set(analysis_listbox.curselection()) analysis = filtered_data[analysis_index.get()] interactive_update(analysis, fit_type, baseline_type, figure, canvas, stats_frame) analysis_listbox.unbind("<<ListboxSelect>>") highlight_current_analysis(analysis_listbox, analysis_index, filtered_data) analysis_listbox.bind("<<ListboxSelect>>", lambda event: on_analysis_select(analysis_index, analysis_listbox, filtered_data, fit_type, baseline_type, figure, canvas, stats_frame, prev_button, next_button, view_button) )