I am using Bryan Oakley's code at Tkinter adding line number to text widget to create a Text
widget with line numbers. But I want create a custom widget that I can use like Text
widget but then with optional line numbers. Like in:
t = LinedText(top)
t.insert("insert", "Hello")
t.show()
But as of now, when I show line numbers, it covers text widget. The window is resized automatically. Why is that happening? My code:
import tkinter as tk
class TextLineNumbers(tk.Canvas):
def __init__(self, *args, **kwargs):
tk.Canvas.__init__(self, *args, **kwargs)
self.textwidget = None
def attach(self, text_widget):
self.textwidget = text_widget
def redraw(self, *args):
'''redraw line numbers'''
self.delete("all")
i = self.textwidget.index("@0,0")
while True :
dline= self.textwidget.dlineinfo(i)
if dline is None: break
y = dline[1]
linenum = str(i).split(".")[0]
self.create_text(2,y,anchor="nw", text=linenum)
i = self.textwidget.index("%s+1line" % i)
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
self.tk.eval('''
proc widget_proxy {widget widget_command args} {
# call the real tk widget command with the real args
set result [uplevel [linsert $args 0 $widget_command]]
# generate the event for certain types of commands
if {([lindex $args 0] in {insert replace delete}) ||
([lrange $args 0 2] == {mark set insert}) ||
([lrange $args 0 1] == {xview moveto}) ||
([lrange $args 0 1] == {xview scroll}) ||
([lrange $args 0 1] == {yview moveto}) ||
([lrange $args 0 1] == {yview scroll})} {
event generate $widget <<Change>> -when tail
}
# return the result from the real widget command
return $result
}
''')
self.tk.eval('''
rename {widget} _{widget}
interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
'''.format(widget=str(self)))
class LinedText(CustomText):
def __init__(self, *args, **kwargs):
CustomText.__init__(self, *args, **kwargs)
self.settings = self.Settings()
self.linenumbers = None
self.text = super()
self.vsb = tk.Scrollbar(orient="vertical", command=self.yview)
self.vsb.pack(side="right", fill="y")
self.text.configure(yscrollcommand=self.vsb.set)
self.text.tag_configure("bigfont", font=("Helvetica", "24", "bold"))
self.text.pack(side="left", fill="both", expand=True)
self.text.bind("<<Change>>", self._on_change)
self.text.bind("<Configure>", self._on_change)
self.text.insert("end", "one\ntwo\nthree\n")
self.text.insert("end", "four\n",("bigfont",))
self.text.insert("end", "five\n")
self.text.pack(side="left")
def hide(self,event=None):
if not self.settings.hide_linenumbers:
self.settings.hide_linenumbers = True
self.linenumbers.pack_forget()
def show(self,event=None):
if self.linenumbers == None:
self.linenumbers = TextLineNumbers(self, width=30)
self.linenumbers.attach(self.text)
self.linenumbers.pack(side="left", fill="y")
elif self.settings.hide_linenumbers:
self.settings.hide_linenumbers = False
self.linenumbers.pack(side="left", fill="y")
def _on_change(self, event):
if self.linenumbers:
self.linenumbers.redraw()
class Settings():
def __init__(self):
self.hide_linenumbers = True
if __name__ == "__main__":
root = tk.Tk()
text = LinedText(root)
#text.pack(side="right", fill="both", expand=True)
button = tk.Button(root, text="Hide", command=text.hide)
button.pack()
button = tk.Button(root, text="Show", command=text.show)
button.pack()
root.mainloop()
Also, I was assuming with .pack()
as opposed to .pack(side="left")
, widget was to be drawn below previous widgets. My buttons are being drawn to right. How do I get them to draw below text and line widgets? Do I absolutely need to use .grid()
or Frame
?