I am creating an autocomplete popup in Python using a Listbox which pops up. One can navigate by using arrows and using Enter to select or use the mouse to select an option. The arrow navigation is working perfectly; however, when I use the mouse to select an option and I want to use the arrows again, it automatically selects the second item on the list. Here is the code:
import tkinter as tk
masterlist = ["Daniel", "Ronel", "Dana", "Elani", "Isabel", "Hercules", "Karien", "Amor", "Piet", "Koos", "Jan", "Johan", "Denise", "Jean", "Petri"]
global listbox_wiggie_index
def new_list_for_listbox(*args):
# When text in entry.wiggie is changed,
# "search_var = tk.StringVar()" will change, which will activate this function.
global listbox_wiggie_index
listbox_wiggie_index = -1 # Reset index every time entry_wiggie changes so listbox is updated
# Clean listbox_wiggie
listbox_wiggie.delete(0, tk.END)
# Get value of changeable search_var.
t = search_var.get()
if t == "":
# Hide listbox_wiggie
# If "listbox_wiggie.destroy()" is used, the widget will have to be created again
listbox_wiggie.place_forget()
return
else:
# Populate listbox_wiggie
t = search_var.get()
for e in masterlist:
if t.lower() in e.lower():
listbox_wiggie.insert(tk.END, e)
place_listbox_wiggie()
print(f"|new_list_for_listbox|New list was generated and listbox_wiggie was placed. Index = {listbox_wiggie_index}")
def place_listbox_wiggie():
# The listbox_wiggie should be already populated. It will be placed at the bottom of entry_wiggie
# Get coordinates of bottom left of entry_wiggie.
x = entry_wiggie.winfo_rootx() - root.winfo_rootx()
y = entry_wiggie.winfo_rooty() - root.winfo_rooty() + entry_wiggie.winfo_height()
# Place listbox_wiggie at the bottom of entry_wiggie
listbox_wiggie.place(x=x, y=y)
entry_wiggie.focus_set()
def listbox_mouse_selection(event=None):
# From: "listbox_wiggie.bind("<<ListboxSelect>>", listbox_mouse_selection)"
i = listbox_wiggie.curselection()[0] # i = integer
t = listbox_wiggie.get(i) # text of selection
print("|listbox_mouse_selection| Selection was made using mouse")
write_value_to_label(t)
def write_value_to_label(teks):
# Write selection to label_wiggie
global listbox_wiggie_index
label_wiggie.config(text=teks)
# Hide listbox_wiggie
listbox_wiggie.place_forget()
entry_wiggie.focus_set()
entry_wiggie.delete(0, tk.END)
listbox_wiggie_index = -1
print(f"|write_value_to_label|{teks} was written to label_wiggie")
def change_listbox_selection_after_arrow_press():
# Focus on listbox_wiggie after arrows are pressed
global listbox_wiggie_index
# Remove the selection highlight from every item in the Listbox, starting from index 0 to the last item (tk.END)
listbox_wiggie.selection_clear(0, tk.END)
# Select the new item according to the index "listbox_wiggie_index":
listbox_wiggie.selection_set(listbox_wiggie_index)
# Highlight the item as the active item:
listbox_wiggie.activate(listbox_wiggie_index)
print(f'|change_listbox_selection_after_arrow_press|Selector of listbox_wiggie was changed to = {listbox_wiggie_index}')
def on_entry_wiggie_key_press(event):
# Function is activated by: entry_wiggie.bind("<KeyRelease>", on_entry_wiggie_key_press)
global listbox_wiggie_index
if listbox_wiggie.size() > 0:
if event.keysym == "Down":
print(f'|on_entry_wiggie_key_press|Down was pressed. Before pressing, index = {listbox_wiggie_index}')
listbox_wiggie_index += 1
if listbox_wiggie_index >= listbox_wiggie.size():
listbox_wiggie_index = 0
change_listbox_selection_after_arrow_press()
elif event.keysym == "Up":
print('|on_entry_wiggie_key_press|Up was pressed. Before pressing, index = {listbox_wiggie_index}')
listbox_wiggie_index -= 1
if listbox_wiggie_index < 0:
listbox_wiggie_index = listbox_wiggie.size() - 1
change_listbox_selection_after_arrow_press()
elif event.keysym == "Return":
print('|on_entry_wiggie_key_press|Return was pressed.')
# check to ensure listbox_wiggie_index is valid before calling listbox_wiggie.get()
if 0 <= listbox_wiggie_index < listbox_wiggie.size():
write_value_to_label(listbox_wiggie.get(listbox_wiggie_index))
elif event.keysym == "Escape":
print('|on_entry_wiggie_key_press|Escape was pressed.')
# Clear the listbox
listbox_wiggie.delete(0, tk.END)
listbox_wiggie.place_forget()
entry_wiggie.focus_set()
else:
print(f'|on_entry_wiggie_key_press|{event.keysym} was pressed.')
listbox_wiggie_index = -1
else:
print(f'|on_entry_wiggie_key_press|{event.keysym} was pressed.')
listbox_wiggie_index = -1
print(f'|on_entry_wiggie_key_press|search_var = "{search_var.get()}". Focus = {root.focus_get()}. Indeks = {listbox_wiggie_index}. ...curselection()[0] = {listbox_wiggie.curselection()}')
print()
root = tk.Tk()
root.title("Listbox appear after typing")
root.geometry("400x200")
# search_var is a variable which holds the content of entry_wiggie.
search_var = tk.StringVar()
search_var.trace("w", new_list_for_listbox)
# new_list_for_listbox will only be activated when search_var is changed (when contents of entry_wiggie is changed)
# Create entry_wiggie. Link content to StringVar "search_var"
entry_wiggie = tk.Entry(root, textvariable=search_var, width=60)
entry_wiggie.pack(pady=5)
entry_wiggie.focus_set()
entry_wiggie.bind("<KeyRelease>", on_entry_wiggie_key_press)
# Create listbox_wiggie, but do not place it (yet)
listbox_wiggie = tk.Listbox(root)
listbox_wiggie.bind("<<ListboxSelect>>", listbox_mouse_selection)
listbox_wiggie_index = -1
# Create label_wiggie to display result
label_wiggie = tk.Label(root, text="No option is selected.")
label_wiggie.pack(pady=5)
root.mainloop()