Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ricardj/ec64dd3171caba3e3818a269f6b57ab2 to your computer and use it in GitHub Desktop.
Save ricardj/ec64dd3171caba3e3818a269f6b57ab2 to your computer and use it in GitHub Desktop.
Tkinter - Autocomplete Entry Field
"""
Changes added by ricardj:
-Import now done with tk prefix instead of using the wildcard import.
-Down movement starts at zero.
-Now up and down movement is cyclic.
-Added return function in order to use the autocomplete-listbox as a tag selector. With out leaving the keyboard the user is going to be able to write with autocomplete and select some tags fast.
-List height reimplemented to make it adapt to the number of matching words. It would be great to define a max listbox length and not to fix the listbox length from the begining.
"""
#from tkinter import *
import tkinter as tk
import re
class AutocompleteEntry(tk.Entry):
def __init__(self, autocompleteList, *args, **kwargs):
self.listboxLength = 0
# Custom matches function
if 'matchesFunction' in kwargs:
self.matchesFunction = kwargs['matchesFunction']
del kwargs['matchesFunction']
else:
def matches(fieldValue, acListEntry):
pattern = re.compile(
'.*' + re.escape(fieldValue) + '.*', re.IGNORECASE)
return re.match(pattern, acListEntry)
self.matchesFunction = matches
# Custom return function
if 'returnFunction' in kwargs:
self.returnFunction = kwargs['returnFunction']
del kwargs['returnFunction']
else:
def selectedValue(value):
print(value)
self.returnFunction = selectedValue
tk.Entry.__init__(self, *args, **kwargs)
self.focus()
self.autocompleteList = autocompleteList
self.var = self["textvariable"]
if self.var == '':
self.var = self["textvariable"] = tk.StringVar()
self.var.trace('w', self.changed)
self.bind("<Right>", self.selection)
self.bind("<Up>", self.moveUp)
self.bind("<Down>", self.moveDown)
self.bind("<Return>", self.select)
self.bind("<Escape>", self.deleteListbox)
self.listboxUp = False
def deleteListbox(self, event=None):
if self.listboxUp:
self.listbox.destroy()
self.listboxUp = False
def select(self, event=None):
if self.listboxUp:
index = self.listbox.curselection()[0]
value = self.listbox.get(tk.ACTIVE)
self.listbox.destroy()
self.listboxUp = False
self.delete(0, tk.END)
self.returnFunction(value)
def changed(self, name, index, mode):
if self.var.get() == '':
self.deleteListbox()
else:
words = self.comparison()
if words:
if not self.listboxUp:
self.listboxLength = len(words)
self.listbox = tk.Listbox(
width=self["width"], height=self.listboxLength)
self.listbox.bind("<Button-1>", self.selection)
self.listbox.bind("<Right>", self.selection)
self.listbox.place(
x=self.winfo_x(), y=self.winfo_y() + self.winfo_height())
self.listboxUp = True
else:
self.listboxLength = len(words)
self.listbox.config(height=self.listboxLength)
self.listbox.delete(0, tk.END)
for w in words:
self.listbox.insert(tk.END, w)
else:
self.deleteListbox()
def selection(self, event):
if self.listboxUp:
self.var.set(self.listbox.get(tk.ACTIVE))
self.listbox.destroy()
self.listboxUp = False
self.icursor(tk.END)
def moveUp(self, event):
if self.listboxUp:
if self.listbox.curselection() == ():
index = '0'
else:
index = self.listbox.curselection()[0]
self.listbox.selection_clear(first=index)
index = str(int(index) - 1)
if int(index) == -1:
index = str(self.listboxLength-1)
self.listbox.see(index) # Scroll!
self.listbox.selection_set(first=index)
self.listbox.activate(index)
def moveDown(self, event):
if self.listboxUp:
if self.listbox.curselection() == ():
index = '-1'
else:
index = self.listbox.curselection()[0]
if index != tk.END:
self.listbox.selection_clear(first=index)
if int(index) == self.listboxLength-1:
index = "0"
else:
index = str(int(index)+1)
self.listbox.see(index) # Scroll!
self.listbox.selection_set(first=index)
self.listbox.activate(index)
def comparison(self):
return [w for w in self.autocompleteList if self.matchesFunction(self.var.get(), w)]
if __name__ == '__main__':
autocompleteList = ['Dora Lyons (7714)', 'Hannah Golden (6010)', 'Walker Burns (9390)', 'Dieter Pearson (6347)', 'Allen Sullivan (9781)', 'Warren Sullivan (3094)', 'Genevieve Mayo (8427)', 'Igor Conner (4740)', 'Ulysses Shepherd (8116)', 'Imogene Bullock (6736)', 'Dominique Sanchez (949)', 'Sean Robinson (3784)', 'Diana Greer (2385)', 'Arsenio Conrad (2891)', 'Sophia Rowland (5713)', 'Garrett Lindsay (5760)', 'Lacy Henry (4350)', 'Tanek Conley (9054)', 'Octavia Michael (5040)', 'Kimberly Chan (1989)', 'Melodie Wooten (7753)', 'Winter Beard (3896)', 'Callum Schultz (7762)', 'Prescott Silva (3736)', 'Adena Crane (6684)', 'Ocean Schroeder (2354)', 'Aspen Blevins (8588)', 'Allegra Gould (7323)', 'Penelope Aguirre (7639)', 'Deanna Norman (1963)', 'Herman Mcintosh (1776)', 'August Hansen (547)', 'Oscar Sanford (2333)', 'Guy Vincent (1656)', 'Indigo Frye (3236)', 'Angelica Vargas (1697)', 'Bevis Blair (4354)', 'Trevor Wilkinson (7067)', 'Kameko Lloyd (2660)', 'Giselle Gaines (9103)', 'Phyllis Bowers (6661)', 'Patrick Rowe (2615)', 'Cheyenne Manning (1743)', 'Jolie Carney (6741)', 'Joel Faulkner (6224)', 'Anika Bennett (9298)', 'Clayton Cherry (3687)', 'Shellie Stevenson (6100)', 'Marah Odonnell (3115)',
'Quintessa Wallace (5241)', 'Jayme Ramsey (8337)', 'Kyle Collier (8284)', 'Jameson Doyle (9258)', 'Rigel Blake (2124)', 'Joan Smith (3633)', 'Autumn Osborne (5180)', 'Renee Randolph (3100)', 'Fallon England (6976)', 'Fallon Jefferson (6807)', 'Kevyn Koch (9429)', 'Paki Mckay (504)', 'Connor Pitts (1966)', 'Rebecca Coffey (4975)', 'Jordan Morrow (1772)', 'Teegan Snider (5808)', 'Tatyana Cunningham (7691)', 'Owen Holloway (6814)', 'Desiree Delaney (272)', 'Armand Snider (8511)', 'Wallace Molina (4302)', 'Amela Walker (1637)', 'Denton Tillman (201)', 'Bruno Acevedo (7684)', 'Slade Hebert (5945)', 'Elmo Watkins (9282)', 'Oleg Copeland (8013)', 'Vladimir Taylor (3846)', 'Sierra Coffey (7052)', 'Holmes Scott (8907)', 'Evelyn Charles (8528)', 'Steel Cooke (5173)', 'Roth Barrett (7977)', 'Justina Slater (3865)', 'Mara Andrews (3113)', 'Ulla Skinner (9342)', 'Reece Lawrence (6074)', 'Violet Clay (6516)', 'Ainsley Mcintyre (6610)', 'Chanda Pugh (9853)', 'Brody Rosales (2662)', 'Serena Rivas (7156)', 'Henry Lang (4439)', 'Clark Olson (636)', 'Tashya Cotton (5795)', 'Kim Matthews (2774)', 'Leilani Good (5360)', 'Deirdre Lindsey (5829)', 'Macy Fields (268)', 'Daniel Parrish (1166)', 'Talon Winters (8469)']
def matches(fieldValue, acListEntry):
pattern = re.compile(re.escape(fieldValue) + '.*', re.IGNORECASE)
return re.match(pattern, acListEntry)
root = tk.Tk()
entry = AutocompleteEntry(
autocompleteList, root, width=32, matchesFunction=matches)
entry.grid(row=0, column=0)
tk.Button(text='Python').grid(column=0)
tk.Button(text='Tkinter').grid(column=0)
tk.Button(text='Regular Expressions').grid(column=0)
tk.Button(text='Fixed bugs').grid(column=0)
tk.Button(text='New features').grid(column=0)
tk.Button(text='Check code comments').grid(column=0)
root.mainloop()
@ricardj
Copy link
Author

ricardj commented Jun 17, 2019

Changes:
-Import now done with tk prefix instead of using the wildcard importing.
-Down movement starts at zero.
-Now up and down movement is cyclic.
-Added return function in order to use the autocomplete-listbox as a tag selector. With out leaving the keyboard the user is going to be able to write with autocomplete and select some tags fast.
-List height reimplemented to make it adapt to the number of matching words. It would be great to define a max listbox length and not to fix the listbox length from the begining.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment