Home > front end >  AutoComplete with backspacing tkinter
AutoComplete with backspacing tkinter

Time:04-12

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

  • Related