Tkinter dropdown menu with search/filter

Python 2.7.13 (yes it is old, no I cannot update)

I have a long list of 100 items of which I need the user to choose 1.

I know Tkinter has a drop down menu, however 100 items is a lot, so it would be better if it had some kind of search function, or at least a filter.
You know the one where you just put the first few letters or numbers and it throws your selector right to the closest option. Like when selecting a country on a website.

Thank you for any and all suggestions!

You can have a entry box for the filter and a list box for the items, updating the list box when the contents of the entry box changes (use the .after method to check every 1/4 seconds or so whether the contents of the entry box has changed).

1 Like

That definitely seems like a great solution.
If anyone else comes up with something different I’ll be glad to hear it since its gonna take a while for me to figure out how to do this.

Thank you very much again!

Here’s a simple example, tested in Python 3.10 but not in Python 2.7:

import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        self.title('Example of a filtered listbox')

        # Full screen.
        self.state('zoomed')

        # 3 rows x 2 columns grid.
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=0)
        self.grid_rowconfigure(0, weight=0)
        self.grid_rowconfigure(1, weight=1)
        self.grid_rowconfigure(2, weight=0)

        # Put the filter in a frame at the top spanning across the columns.
        frame = tk.Frame(self)
        frame.grid(row=0, column=0, columnspan=2, sticky='we')

        # Put the filter label and entry box in the frame.
        tk.Label(frame, text='Filter:').pack(side='left')

        self.filter_box = tk.Entry(frame)
        self.filter_box.pack(side='left', fill='x', expand=True)

        # A listbox with scrollbars.
        yscrollbar = tk.Scrollbar(self, orient='vertical')
        yscrollbar.grid(row=1, column=1, sticky='ns')

        xscrollbar = tk.Scrollbar(self, orient='horizontal')
        xscrollbar.grid(row=2, column=0, sticky='we')

        self.listbox = tk.Listbox(self)
        self.listbox.grid(row=1, column=0, sticky='nswe')

        yscrollbar.config(command=self.listbox.yview)
        xscrollbar.config(command=self.listbox.xview)

        # The current filter. Setting it to None initially forces the first update.
        self.curr_filter = None

        # All of the items for the listbox.
        self.items = [str(i) for i in range(100)]

        # The initial update.
        self.on_tick()

    def on_tick(self):
        if self.filter_box.get() != self.curr_filter:
            # The contents of the filter box has changed.
            self.curr_filter = self.filter_box.get()

            # Refresh the listbox.
            self.listbox.delete(0, 'end')

            for item in self.items:
                if self.curr_filter in item:
                    self.listbox.insert('end', item)

        self.after(250, self.on_tick)

App().mainloop()
1 Like

This is basically all I need. I will just add a “Enter” button for the final choise or something.

I have no idea how you made it so quickly but I thank you a lot!

FYI the script worked right away, just the name “tkinter” is supposed to be “Tkinter” on my version of Python.

I’ve written a number of tools for myself in tkinter, so I already had some code to work from! :slight_smile:

1 Like

The script works very nicely, however the scrollbar… bar is very very tiny and when I move it it moves through the list correctly, but jumps right back to the position in the image, although it does leave the listbox itself in the position where I left it and it does NOT jump back up, which is correct.

What affects the size of the bar and why is it so small?
(there is text in the listbox I just removed it in Paint)

    def __init__(window):
        tk.Tk.__init__(window)
        window.attributes('-topmost', True)
        window.update()
        window.title('Select desired read function')

        # 3 rows x 2 columns grid.
        window.grid_columnconfigure(0, weight=1)
        window.grid_columnconfigure(1, weight=0)
        window.grid_rowconfigure(0, weight=0)
        window.grid_rowconfigure(1, weight=1)
        window.grid_rowconfigure(2, weight=0)
        w = 320
        h = 400
        x = 600
        y = 130
        window.geometry('%dx%d+%d+%d' % (w, h, x, y))
        #window.bind_all("<MouseWheel>", window._on_mousewheel)

        # Put the filter in a frame at the top spanning across the columns.
        frame = tk.Frame(window)
        frame.grid(row=0, column=0, columnspan=2, sticky='we')

        # Put the filter label and entry box in the frame.
        tk.Label(frame, text='Filter:').pack(side='left')

        window.filter_box = tk.Entry(frame)
        window.filter_box.pack(side='left', fill='x', expand=True)

        # A listbox with scrollbars.
        yscrollbar = tk.Scrollbar(window, orient='vertical')
        yscrollbar.grid(row=1, column=1, sticky='ns')

        window.listbox = tk.Listbox(window)
        window.listbox.grid(row=1, column=0, sticky='nswe')
        tk.Button(frame, text="Submit", command = get_selected_parameter).pack(side='right')  # submit button
        window.listbox.bind('<Double-Button>', get_selected_parameter_click)  # double-click function

        yscrollbar.set
        yscrollbar.config(command=window.listbox.yview)

        # The current filter. Setting it to None initially forces the first update.
        window.curr_filter = None

        # All of the items for the listbox.
        window.items = read_parameters

        # The initial update.
        window.on_tick()

    def on_tick(window):
        if window.filter_box.get() != window.curr_filter:
            # The contents of the filter box has changed.
            window.curr_filter = window.filter_box.get()

            # Refresh the listbox.
            window.listbox.delete(0, 'end')

            for item in window.items:
                if window.curr_filter.upper() in item:
                    window.listbox.insert('end', item)

        window.after(250, window.on_tick)

You need:

window.listbox = tk.Listbox(window, yscrollcommand=yscrollbar.set)

because the line:

yscrollbar.config(command=window.listbox.yview)

tells the scrollbar to monitor the listbox, but it doesn’t tell the listbox to monitor the scrollbar.

You also have an extraneous:

yscrollbar.set

which isn’t doing anything.

1 Like

Perfect! That was exactly the issue.
Thank you so much! Gotta love some expert help every now and then :smile: