I am making an IDE and had asked for some help with getting a special widget that could give autocomplete. Thanks to @BryanOakley here I was able to get that help. The widget works well but it has a couple issues that I can't seem to figure out on my own. One of these problems is that once the autocomplete code finds a match it inserts the rest of the code and it prevents the user from removing the previous character. It looks like it should work as is given that it puts the insert to where it was left off but when I attempt to remove the last character with the Delete button it doesn't. I am unsure of how to fix this. How could I go about doing this?
The autocomplete Text widget:
from tkinter import *
class AutocompleteText(Frame):
def __init__(self, *args, **kwargs):
self.callback = kwargs.pop("autocomplete", None)
super().__init__(*args, **kwargs)
Frame.__init__(self, *args, **kwargs)
self.text = Text(self, font=("Courier New bold", 15), wrap=NONE, undo=True)
self.text.pack(side=LEFT, fill=BOTH, expand=True)
self.text.bind("<Any-KeyRelease>", self._autocomplete)
self.text.bind("<Tab>", self._handle_tab)
def _handle_tab(self, event):
tag_ranges= self.text.tag_ranges("autocomplete")
if tag_ranges:
self.text.mark_set("insert", tag_ranges[1])
self.text.tag_remove("sel", "1.0", "end")
self.text.tag_remove("autocomplete", "1.0", "end")
return "break"
def _autocomplete(self, event):
if event.char and self.callback:
word = self.text.get("insert-1c wordstart", "insert-1c wordend")
matches = self.callback(word)
if matches:
remainder = matches[0][len(word):]
insert = self.text.index("insert")
self.text.insert(insert, remainder, ("sel", "autocomplete"))
self.text.mark_set("insert", insert)
def get_matches(word):
#Returns possible matches
#words is a list of almost every keyword and builtin function
words=["False", "await", "else", "import", "pass", "None", "break", "except", "in", "raise", "True", "class", "finally", "is", "return", "and", "continue", "for", "lambda", "try", "as", "def", "from", "nonlocal", "while", "assert", "del", "global", "not", "with", "async", "elif", "if", "or", "yield", "abs", "all", "any", "ascii", "bin", "bool", "bytearray", "bytes", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "exec", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip"]
matches = [x for x in words if x.startswith(word)]
return matches
root = Tk()
text = AutocompleteText(root, autocomplete=get_matches)
text.pack()
root.mainloop()
CodePudding user response:
I searched many methods but exact solution dint got so I proceed with own method , I hope this may help You or get an idea but some drawbacks are there,
from tkinter import *
root = Tk()
root.geometry('200x300')
name = StringVar()
search_box_list = ['apple', 'ball', 'cat', 'dog', 'eagle'] # this is list data to select
def getLIstBoxData(list_data='', list_box='', entry_box=None, set_variable='', x_axis=0, y_axis=0, width=0):
def destroyListBox(event):
"""this is the command when the list box to close"""
try:
list_box.place_forget()
except BaseException:
pass
def searchIList(event):
"""this gives the list box where the data no are aligned"""
# its align list box and bind some methods to it
list_box.config(width=width)
list_box.place(x=x_axis, y=y_axis)
list_box.bind('<Leave>', destroyListBox)
list_box.bind('<Double-1>', itemsSelected)
list_box.bind('<Return>', itemsSelected)
if search_box_list is not None:
# this checks for the match in phone numbers list and assigned as matched list
match = [i for i in search_box_list if (name.get().lower() or name.get().capitalize()) in i]
# for inserting new data delete the old data
list_box.delete(0, END)
# this for add data to the listbox
for c in match:
list_box.insert(END, c)
if not match:
destroyListBox(None)
if name.get() == "":
destroyListBox(None)
def itemsSelected(event):
"""when the no is selected from list box it aligned to
the phone number and gives the data"""
# this when selected the data to following happens
for i in list_box.curselection():
set_variable.set(list_box.get(i))
# after selecting the data and closing the list box
destroyListBox(None)
entry_box.bind("<KeyRelease>", searchIList)
entry_box.bind("<Down>", lambda event: list_box.focus_set())
entry_box = Entry(root, width=20, textvariable=name)
entry_box.pack(padx=30, pady=30)
Label(root, width=20, textvariable=name).pack(pady=20, padx=30)
list_box = Listbox(root, height=10, width=27)
getLIstBoxData(search_box_list, list_box, entry_box, name, 38, 52, 20)
root.mainloop()
This works good but down button don't not select the list first item immediately, may be some suggestion will improve this code
CodePudding user response:
I figured it out. Here's the code:
from tkinter import *
class AutocompleteText(Frame):
def __init__(self, *args, **kwargs):
self.callback = kwargs.pop("autocomplete", None)
super().__init__(*args, **kwargs)
Frame.__init__(self, *args, **kwargs)
self.text = Text(self, font=("Courier New bold", 15), wrap=NONE, undo=True)
self.text.pack(side=LEFT, fill=BOTH, expand=True)
self.text.bind("<Any-KeyRelease>", self._autocomplete)
self.text.bind("<Tab>", self._handle_tab)
def _handle_tab(self, event):
tag_ranges= self.text.tag_ranges("autocomplete")
if tag_ranges:
self.text.mark_set("insert", tag_ranges[1])
self.text.tag_remove("sel", "1.0", "end")
self.text.tag_remove("autocomplete", "1.0", "end")
return "break"
def _autocomplete(self, event):
if event.char and self.callback and event.keysym != "BackSpace":
word = self.text.get("insert-1c wordstart", "insert-1c wordend")
matches = self.callback(word)
if matches:
remainder = matches[0][len(word):]
insert = self.text.index("insert")
self.text.insert(insert, remainder, ("sel", "autocomplete"))
self.text.mark_set("insert", insert)
def get_matches(word):
#Returns possible matches
#words is a list of almost every keyword and builtin function
words=["False", "await", "else", "import", "pass", "None", "break", "except", "in", "raise", "True", "class", "finally", "is", "return", "and", "continue", "for", "lambda", "try", "as", "def", "from", "nonlocal", "while", "assert", "del", "global", "not", "with", "async", "elif", "if", "or", "yield", "abs", "all", "any", "ascii", "bin", "bool", "bytearray", "bytes", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "exec", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip"]
matches = [x for x in words if x.startswith(word)]
return matches
root = Tk()
text = AutocompleteText(root, autocomplete=get_matches)
text.pack()
root.mainloop()
This works because when you give an event it has a value to tell what key was pressed. By using this value we can make a backspacing edge case