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()