Home > Net >  Suppressing insert of tkinter Text while still allowing other binds
Suppressing insert of tkinter Text while still allowing other binds

Time:07-12

I need to be able to bind <Tab> and not have it insert the tab all while not using return "break". Is there a way to stop the bind of a keypress inserting its character while still allowing other binds? I will not be able to place it at the end of my binds because I plan on building more specialized Text widgets with their own functions and I don't want to have to have boilerplate code. I know I could also wait until <keyReale-Tab> and not use it but then it is visible and I need this to be instant

Current code:

class BaseText(Text):
    def __init__(self, master, **kwargs):
        #Create variables for setup
        self.master = master

        #Initiate setup
        Text.__init__(self, self.master, **kwargs)

        #Set groundwork for indentation
        self.indentation_type = "Space"
        self.indentation_amount = 4

        #Set font to look right in case indentation type is "Tab"
        font = Font(font=self["font"])
        tab_width = font.measure(" "*self.indentation_amount)
        self.config(tabs=(tab_width))

        #Create binds
        self.bind("<KeyRelease-Tab>", self.handle_tab, add=True) #Need to make this instant with <Tab>
        #self.bind("<Tab>", self.handle_tab, add=True)

    def handle_tab(self, *args):
        if self.indentation_type == "Space":
            self.delete("insert-1c") #Removes the tab (Needs to be instant with <Tab> but I can't use `return "break"`)
            #Add the tab and if there are multiple lines selected then we do them all
            if self.tag_ranges("sel") != ():
                first_line = int(self.index("sel.first").split(".")[0])
                last_line = int(self.index("sel.last").split(".")[0])
                for line in range(first_line, last_line-1): #Use -1 so that it works for single lines or without sel
                    self.insert(str(line)   ".0", " "*self.indentation_amount)
                    self.tag_add("sel", str(line)   ".0", str(line)   "."   str(self.indentation_amount))
            #Get the last or only line
            self.insert("insert", " "*(self.indentation_amount))
            # I need to ensure that the tab isn't placed all while not using return "break" so that more stuff can be binded to <Tab>. If stuff can still be binded with it only typing four spaces  and using `return "break"` that would be great

The main reason why using return "break" is unusable is because I am creating an IDE. For this IDE I am making with someone else so my code can't have a lot of boilerplate or repeated pieces. Because of that I am creating the BaseText class and then all language specific variants are built on top of it and its functions. Because other classes are being built on top of it that means that the <Tab> keybind is likely to be used again and if I use return "break" those binds will never run.

CodePudding user response:

What inserts the tab after your binding is the class binding of the Text widget. You can override this class binding to prevent this without affecting any direct binding (which is executed before the class binding)

 <any_tk_widget>.bind_class("Text", "<Tab>", tabkey)

However, the new class binding tabkey() will be executed everytime tab is pressed in any Text widgets. So if you also use normal Text widgets in your project you need to also keep the usual behavior for them:

def tabkey(event):
    if isinstance(event.widget, BaseText):
        return "break"  # don't insert tab
    # standard Text class binding to <Tab>
    event.widget.insert("insert", "\t")
    event.widget.focus_set()
    return "break"

Below is an example where you can compare the behavior of the BaseText and Text widgets when binding the insertion of "TAB" when the user presses Tab

import tkinter as tk

class BaseText(tk.Text):

    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)


def tabkey(event):
    if isinstance(event.widget, BaseText):
        return "break"  # don't insert tab
    # standard Text class binding to <Tab>
    event.widget.insert("insert", "\t")
    event.widget.focus_set()
    return "break"


root = tk.Tk()
# override standard Tab class binding
root.bind_class("Text", "<Tab>", tabkey)

bt = BaseText(root)
bt.bind("<Tab>", lambda ev: bt.insert("insert", "TAB"))
bt.pack()
tt = tk.Text(root)
tt.bind("<Tab>", lambda ev: tt.insert("insert", "TAB"))
tt.pack()

EDIT: Here is a self-contained version where the binding is inside the BaseText class with an extra class to show that the functionality is compatible with inheritance.

import tkinter as tk

class BaseText(tk.Text):

    _initialized = False

    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)

        if not self._initialized:  # do it only once, not every time a BaseText widget is created
            self.bind_class("Text", "<Tab>", self.tabkey)  # override standard Tab class binding
            BaseText._initialized = True

    @staticmethod
    def tabkey(event):
        if isinstance(event.widget, BaseText):
            return "break"  # don't insert tab
        # standard Text class binding to <Tab>
        event.widget.insert("insert", "\t")
        event.widget.focus_set()
        return "break"


class AdvancedText(BaseText):
    def __init__(self, *args, **kwargs):
        BaseText.__init__(self, *args, **kwargs)
        self.bind("<Tab>", self.custom_tab)

    def custom_tab(self, event):
        self.insert("insert", "....")


root = tk.Tk()

tk.Label(root, text="tk.Text").pack()

tt = tk.Text(root)
tt.bind("<Tab>", lambda ev: tt.insert("insert", "TAB"))
tt.pack()

tk.Label(root, text="BaseText").pack()
bt = BaseText(root)
bt.bind("<Tab>", lambda ev: bt.insert("insert", "TAB"))
bt.pack()

tk.Label(root, text="AdvancedText").pack()
at = AdvancedText(root)
at.pack()
  • Related