Reputation: 574
I have some questions about this Tkinter autocomplete entry by Mitja Martini.
I've removed nonessential code and left the original author's comments. The autocomplete works fine if you type slow enough to let the highlighting take place after each character, but having to type slowly defeats the purpose of autocomplete, since little time is saved. The user will have to watch the flashing highlight or just type very slow if he doesn't want to watch for the flash.
Why can't I type fast without the selection getting unhighlighted and the cursor repositioning to the end of the filled-in word; the result is that fast typing appends characters to the end of the completed word.
How could this be fixed?
What is meant by "a new hit list" (conditional commented out). I'd like to see a simple example of how this would work and/or why I can't just delete this condition.
What's meant by "known hit list" (section commented out). What's an example of an unknown hit list in this code, so I can see the commented lines at work?
import tkinter as tk
class AutocompleteEntry(tk.Entry):
def set_completion_list(self, completion_list):
self._completion_list = completion_list
self._hits = []
self._hit_index = 0
self.position = 0
self.bind('<KeyRelease>', self.handle_keyrelease)
def autocomplete(self):
# 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()):
_hits.append(element)
# 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) % len(self._hits)
# perform the auto completion
if self._hits:
self.delete(0,tk.END)
self.insert(0,self._hits[self._hit_index])
self.select_range(self.position,tk.END)
def handle_keyrelease(self, event):
if len(event.keysym) == 1:
self.autocomplete()
def test(test_list):
root = tk.Tk(className=' AutocompleteEntry demo')
root.geometry('+500+300')
entry = AutocompleteEntry(
root,
fg='white', bg='black',
insertbackground='white',
font=('arial', 30))
entry.set_completion_list(test_list)
entry.grid()
entry.focus_set()
root.mainloop()
if __name__ == '__main__':
test_list = (
'Geronimo', 'Tecumseh', 'onomatopoeia',
'onerous', 'technicality', 'geriatric' )
test(test_list)
Upvotes: 1
Views: 6154
Reputation: 11
import tkinter as tk
class AutocompleteEntry(tk.Entry):
def initialize(self, completion_list):
self.completion_list = completion_list
self.hitsIndex = 0
self.hits = []
self.autocompleted = False
self.current = ""
self.bind('<KeyRelease>', self.act_on_release)
self.bind('<Key>', self.act_on_press)
def autocomplete(self):
self.current = self.get().lower()
self.hits = []
for item in self.completion_list:
if item.lower().startswith(self.current):
self.hits.append(item)
self.hitsIndex = 0
if self.hits:
self.display_autocompletion()
def remove_autocompletion(self):
cursor = self.index(tk.INSERT)
self.delete(cursor, tk.END)
self.autocompleted = False
def display_autocompletion(self):
cursor = self.index(tk.INSERT)
self.delete(0, tk.END)
self.insert(0, self.hits[self.hitsIndex])
self.select_range(cursor, tk.END)
self.icursor(cursor)
self.autocompleted = True
def act_on_release(self, event):
return
def act_on_press(self, event):
if event.keysym in ('Left'):
if self.autocompleted:
self.remove_autocompletion()
return "break"
if event.keysym in ('Down', 'Up', 'Tab'):
if self.select_present():
cursor = self.index(tk.SEL_FIRST)
# print("Cursor",cursor)
# print("In box:",self.get().lower()[0:cursor])
# print("Last entered",self.current)
if len(self.hits) and self.current == self.get().lower()[0:cursor]:
if event.keysym == 'Up':
self.hitsIndex -= 1
else:
self.hitsIndex += 1
self.hitsIndex %= len(self.hits)
self.display_autocompletion()
else:
self.autocomplete()
return "break"
if event.keysym in ('Right'):
if self.select_present():
self.selection_clear()
self.icursor(tk.END)
return "break"
# Example usage:
root = tk.Tk()
chiefs = [
'Sitting Bull', 'Sitting Horse', 'Geronimo', 'Tecumseh', 'Pontiac',
'Red Cloud', 'Crazy Horse', 'Cochise', 'Red Jacket',
'Red Czar', 'Red Czechoslovakian']
auto_entry = AutocompleteEntry(root)
auto_entry.pack()
auto_entry.initialize(chiefs)
root.mainloop()
Upvotes: 1
Reputation: 574
The problem with the autocomplete code is that it relies on replacement of highlighted text so user has to wait for text to highlight before typing the next character. After posting my question, I wrote my own autocomplete from scratch which uses a different technique that allows the user to keep typing after the word has filled in. That doesn't answer the questions as stated above but it solves my immediate problem unless this code has a glitch I haven't found yet. The purpose of this partial answer is to show an autofill that doesn't prevent the user from typing after the word has filled in.
import tkinter as tk
chiefs = [
'Sitting Bull', 'Geronimo', 'Tecumseh', 'Pontiac',
'Red Cloud', 'Crazy Horse', 'Cochise', 'Red Jacket',
'Red Czar', 'Red Czechoslovakian']
def match_string():
hits = []
got = auto.get()
for item in chiefs:
if item.startswith(got):
hits.append(item)
return hits
def get_typed(event):
if len(event.keysym) == 1:
hits = match_string()
show_hit(hits)
def show_hit(lst):
if len(lst) == 1:
auto.set(lst[0])
detect_pressed.filled = True
def detect_pressed(event):
key = event.keysym
if len(key) == 1 and detect_pressed.filled is True:
pos = autofill.index(tk.INSERT)
autofill.delete(pos, tk.END)
detect_pressed.filled = False
root = tk.Tk()
auto = tk.StringVar()
autofill = tk.Entry(
root,
font=('tacoma', 30),
bg='black',
insertbackground='white',
fg='white',
textvariable=auto)
autofill.grid()
autofill.focus_set()
autofill.bind('<KeyRelease>', get_typed)
autofill.bind('<Key>', detect_pressed)
root.mainloop()
Upvotes: 7