Home > Enterprise >  How to restrict entry to only available options in AutocompleteCombobox while being able to type in
How to restrict entry to only available options in AutocompleteCombobox while being able to type in

Time:03-08

I am using AutocompleteCombobox from ttkwidgets.autocomplete for my selection widget. While all the features are good (like being able to filter the list while typing), I want to be able to type in it but only from the available options ie., I should not be able to type in custom values. I tried using state=readonly but it doesn't allow me to type in the combobox. Any solutions would be greatly appreciated.

CodePudding user response:

Since You didn't provide example code and tkinter does not provide default autocomplete combobx, I assumed You are using AutocompleteCombobox from ttkwidgets.autocomplete.

To get only valid entries (no custom one), You have to re-implement autocomplete method of AutocompleteCombobox class.
Logic is simple: check if current user input is in autocomplete list. If not, remove last character and show again last autocomplete suggestion.

I used example code from this source as a base for my answer.

Here is code snippet implementing custom MatchOnlyAutocompleteCombobox:

from tkinter import *

from ttkwidgets.autocomplete import AutocompleteCombobox

countries = [
    'Antigua and Barbuda', 'Bahamas', 'Barbados', 'Belize', 'Canada',
    'Costa Rica ', 'Cuba', 'Dominica', 'Dominican Republic', 'El Salvador ',
    'Grenada', 'Guatemala ', 'Haiti', 'Honduras ', 'Jamaica', 'Mexico',
    'Nicaragua', 'Saint Kitts and Nevis', 'Panama ', 'Saint Lucia',
    'Saint Vincent and the Grenadines', 'Trinidad and Tobago', 'United States of America'
]

ws = Tk()
ws.title('PythonGuides')
ws.geometry('400x300')
ws.config(bg='#8DBF5A')


class MatchOnlyAutocompleteCombobox(AutocompleteCombobox):

    def autocomplete(self, delta=0):
        """
        Autocomplete the Combobox.

        :param delta: 0, 1 or -1: how to cycle through possible hits
        :type delta: int
        """
        if delta:  # need to delete selection otherwise we would fix the current position
            self.delete(self.position, END)
        else:  # set position to end so selection starts where textentry ended
            self.position = len(self.get())
        # collect hits
        _hits = []
        for element in self._completion_list:
            if element.lower().startswith(self.get().lower()):  # Match case insensitively
                _hits.append(element)

        if not _hits:
            # No hits with current user text input
            self.position -= 1  # delete one character
            self.delete(self.position, END)
            # Display again last matched autocomplete
            self.autocomplete(delta)
            return

        # if we have a new hit list, keep this in mind
        if _hits != self._hits:
            self._hit_index = 0
            self._hits = _hits
        # only allow cycling if we are in a known hit list
        if _hits == self._hits and self._hits:
            self._hit_index = (self._hit_index   delta) % len(self._hits)
        # now finally perform the auto completion
        if self._hits:
            self.delete(0, END)
            self.insert(0, self._hits[self._hit_index])
            self.select_range(self.position, END)


frame = Frame(ws, bg='#8DBF5A')
frame.pack(expand=True)

Label(
    frame,
    bg='#8DBF5A',
    font=('Times', 21),
    text='Countries in North America '
).pack()

entry = MatchOnlyAutocompleteCombobox(
    frame,
    width=30,
    font=('Times', 18),
    completevalues=countries
)
entry.pack()

ws.mainloop()
  • Related