-
-
Save ian-weisser/10013110 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3 | |
""" | |
tkentrycomplete.py | |
A tkinter widget that features autocompletion. | |
Created by Mitja Martini on 2008-11-29. | |
Converted to Python3 by Ian weisser on 2014-04-06. | |
""" | |
import sys | |
import os | |
import tkinter | |
__version__ = "1.0" | |
tkinter_umlauts=['odiaeresis', 'adiaeresis', 'udiaeresis', 'Odiaeresis', 'Adiaeresis', 'Udiaeresis', 'ssharp'] | |
class AutocompleteEntry(tkinter.Entry): | |
""" | |
Subclass of tkinter.Entry that features autocompletion. | |
To enable autocompletion use set_completion_list(list) to define | |
a list of possible strings to hit. | |
To cycle through hits use down and up arrow keys. | |
""" | |
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, delta=0): | |
"""autocomplete the Entry, delta may be 0/1/-1 to cycle through possible hits""" | |
if delta: # need to delete selection otherwise we would fix the current position | |
self.delete(self.position, tkinter.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.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 + delta) % len(self._hits) | |
# now finally perform the auto completion | |
if self._hits: | |
self.delete(0,tkinter.END) | |
self.insert(0,self._hits[self._hit_index]) | |
self.select_range(self.position,tkinter.END) | |
def handle_keyrelease(self, event): | |
"""event handler for the keyrelease event on this widget""" | |
if event.keysym == "BackSpace": | |
self.delete(self.index(tkinter.INSERT), tkinter.END) | |
self.position = self.index(tkinter.END) | |
if event.keysym == "Left": | |
if self.position < self.index(tkinter.END): # delete the selection | |
self.delete(self.position, tkinter.END) | |
else: | |
self.position = self.position-1 # delete one character | |
self.delete(self.position, tkinter.END) | |
if event.keysym == "Right": | |
self.position = self.index(tkinter.END) # go to end (no selection) | |
if event.keysym == "Down": | |
self.autocomplete(1) # cycle to next hit | |
if event.keysym == "Up": | |
self.autocomplete(-1) # cycle to previous hit | |
# perform normal autocomplete if event is a single key or an umlaut | |
if len(event.keysym) == 1 or event.keysym in tkinter_umlauts: | |
self.autocomplete() | |
def test(test_list): | |
"""Run a mini application to test the AutocompleteEntry Widget.""" | |
root = tkinter.Tk(className=' AutocompleteEntry demo') | |
entry = AutocompleteEntry(root) | |
entry.set_completion_list(test_list) | |
entry.pack() | |
entry.focus_set() | |
root.mainloop() | |
if __name__ == '__main__': | |
test_list = (u'test', u'type', u'true', u'tree', u'tölz') | |
print("Type a 't' to test the AutocompleteEntry widget.") | |
print("Will use AutocompleteEntry.set_completion_list({})".format(test_list)) | |
print("Try also the backspace key and the arrow keys.") | |
test(test_list) |
Digging a little more, I think I have found an answer here:
http://stackoverflow.com/questions/8942371/tkinter-autocomplete-widget-lowercase-bias
RobB @ StackOverflow suggested this:
Try removing the
.lower()
fromif element.startswith(self.get().lower()):
or to make the match case-insensitive:if element.lower().startswith(self.get().lower()):
which will convert your entry string into lowercase and then the list values to lowercase as well so that a match will be made anytime the same letters are entered even if the case is off.
For me, it seems to be working. I get my previous database entry regardless of case :)
For the kind of usage I was looking for (adding a new item to a SQLite database while getting autocompletion from previous records), there was also something weird about pressing Left key or clicking somewhere in the text and a lot of characters getting deleted. So, for me, this works better:
def handle_keyrelease(self, event):
"""event handler for the keyrelease event on this widget"""
if event.keysym == "BackSpace":
if self.position < self.index(END): # delete the selection
self.delete(self.position, END)
else:
#self.delete(self.index(INSERT), END)
self.position = self.index(END)
if event.keysym == "Left":
if self.position < self.index(END): # delete the selection
self.delete(self.position, END)
#else:
#self.position = self.position-1 # delete one character
#self.delete(self.position, END)
I added an IF clause to the "backspace" key part and I removed the above commented lines in order to allow the user to go back and correct some misspelling. Now if the user goes back and edits something, the rest of the previously entered text will be intact and the text only gets deleted if the user presses (deletes only the remaining autocompletion but leaves the rest intact) or backspace (deletes autocompletion and one character at a time, as expected). If the user mouse-clicks in the text, nothing is deleted.
#Help
##Need it same a son tkinter text widget
Can anyone help me, I want same it on a tkinter text widget . It awesome I need it because I am making a text editor.
Thank you bro ;)
Hi. I was looking for a tkinter widget like this and got really excited when I discovered this page. However, I am getting a strange behavior: if the list includes items that start with uppercase characters, or it won't work for those items. Only words that start by lowercase or numeric characters will be shown on the widget autocompletion. I am still trying to understand why this happens. Not sure if it was intentional, but it it was, it would be great to include a way to opt for "case-insensitiveness".