Searchable Tkinter Combobox with visible Dropdown while editing

Tkinter is not the most modern GUI library out there, and can be a bit lacking in certain features that are now common across modern GUIs. For example, a popular feature which we see in modern dropdowns (combo boxes) is that they are “searchable”. This means that if there are 30 options available in the combobox, you can filter through them by typing into the combobox (e.g. typing P will filter out all those options which do not start with P). In this tutorial, we will work on developing a custom searchable Tkinter combobox.

Another vital aspect we will address is ensuring that the dropdown remains visible while typing into the combobox.. Otherwise we won’t know which options are being filtered out as we type. This is an important feature to completing our vision of an ideal combobox.

Since Tkinter does not offer such a feature natively, and the combobox widget cannot be easily modified to encompass such changes, we will be making our own widget using the base Tkinter widgets.


Boilerplate Code

We have alot of code to cover in this tutorial, so lets first get all the simple boilerplate code out of the way. The following code is responsible for setting up the UI behind our new combobox, and binding the appropriate events to methods that we will soon define. Our combobox consists of two widgets, the entry widget (where the current option is displayed) and the listbox widget (where all options are displayed). The entry widget can also be edited for us to make “searches” through the list of options.

Python
import tkinter as tk
from PIL import Image, ImageTk

class SearchableComboBox():
    def __init__(self, options) -> None:
        self.dropdown_id = None
        self.options = options

        # Create a Text widget for the entry field
        wrapper = tk.Frame(root)
        wrapper.pack()

        self.entry = tk.Entry(wrapper, width=24)
        self.entry.bind("<KeyRelease>", self.on_entry_key)
        self.entry.bind("<FocusIn>", self.show_dropdown) 
        self.entry.pack(side=tk.LEFT)

        # Dropdown icon/button
        self.icon = ImageTk.PhotoImage(Image.open("dropdown_arrow.png").resize((16,16)))
        tk.Button(wrapper, image=self.icon, command=self.show_dropdown).pack(side=tk.LEFT)

        # Create a Listbox widget for the dropdown menu
        self.listbox = tk.Listbox(root, height=5, width=30)
        self.listbox.bind("<<ListboxSelect>>", self.on_select)
        for option in self.options:
            self.listbox.insert(tk.END, option)

    def on_entry_key(self, event):
        pass

    def on_select(self, event):
        pass

    def show_dropdown(self, event=None):
        pass

    def hide_dropdown(self):
        pass

# Create the main window
root = tk.Tk()
root.title("Searchable Dropdown")

options = ["Apple", "Banana", "Cherry", "Date", "Grapes", "Kiwi", "Mango", "Orange", "Peach", "Pear"]
SearchableComboBox(options)

# Run the Tkinter event loop
root.geometry('220x150')
root.mainloop()

It is important to understand the purpose of the event bindings used in this code.

  1. The <KeyRelease> event is triggered whenever a keyboard key is pressed and released. We will use this event to trigger a function which shows the dropdown temporarily, and also sorts the options based on similarity to the currently typed value in the entry widget.
  2. The <FocusIn> event triggers when we click on the entry widget. This will trigger the dropdown menu temporarily so the user can see the options.

There is also the on_select method which is triggered when an option in the listbox (a.k.a dropdown) is selected. All this does, is deletes the current text in the entry, and inserts the text which you just selected.

Python
    def on_select(self, event):
        selected_index = self.listbox.curselection()
        if selected_index:
            selected_option = self.listbox.get(selected_index)
            self.entry.delete(0, tk.END)
            self.entry.insert(0, selected_option)

Adding the Filter Feature

Time to add the filter feature into our code. For this, we define the on_entry_key method. This method is called whenever a key is pressed and released. We want this behavior so that as the user types into the entry widget, the dropdown values update, and the list of options is displayed.

Python
    def on_entry_key(self, event):
        typed_value = event.widget.get().strip().lower()
        if not typed_value:
            # If the entry is empty, display all options
            self.listbox.delete(0, tk.END)
            for option in self.options:
                self.listbox.insert(tk.END, option)
        else:
            # Filter options based on the typed value
            self.listbox.delete(0, tk.END)
            filtered_options = [option for option in self.options if option.lower().startswith(typed_value)]
            for option in filtered_options:
                self.listbox.insert(tk.END, option)
        self.show_dropdown()

Feel free to customize the filtering logic as you want. If you remove the startswith method, then it will become substring based filtering instead. E.g. if you typed in “pe”, then all options with the substring “pe” will be filtered.

Also note that the show_dropdown method is called at the end.


Making the dropdown visible

Now for the hardest part, which is the feature that the native Tkinter combobox does not support. The ability to show the dropdown, while keeping focus on the entry widget and allowing us to type simultaneously.

Python
    def show_dropdown(self, event=None):
        self.listbox.place(in_=self.entry, x=0, rely=1, relwidth=1.0, anchor="nw")
        self.listbox.lift()

        # Show dropdown for 2 seconds
        if self.dropdown_id: # Cancel any old events
            self.listbox.after_cancel(self.dropdown_id)
        self.dropdown_id = self.listbox.after(2000, self.hide_dropdown)

    def hide_dropdown(self):
        self.listbox.place_forget()

We have used the Tkinter “after” feature here to make this magic possible. But we do not want to show the dropdown permanently (otherwise it would never go away), so we make it timer based instead. The timer has been set at 2 seconds, so feel free to adjust this based on your preferences. If two keys are pressed with 1 seconds intervals, then the duration will refresh, and the dropdown will continue to be shown for two seconds after the last key was released.

This particular line of code is also very important.

Python
self.listbox.place(in_=self.entry, x=0, rely=1, relwidth=1.0, anchor="nw")

It places the listbox at the location of the entry widget, using the place layout manager. It is essential to use place, not pack or grid, since the listbox should be able to become visible without effecting the layout. Pack layout manager for instance, would have effected the surrounding widgets, and maybe even the window itself (if there wasn’t enough space below for it show).

With this, our Searchable Tkinter Combobox is now complete.

Searchable Tkinter Combobox with visible Dropdown while editing

This marks the end of the Searchable Tkinter Combobox Tutorial. Any comments or suggestions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments