As per the question title: Is it possible to have a red squiggly line appear under words in a Tkinter text widget without using a canvas widget? (The same squiggle as when you misspell a word)
I'm going for something like this:
If so where would I start?
To enter multiple lines of text, use the Text widget. Entry in the Tkinter reference starts with: The purpose of an Entry widget is to let the user see and modify a single line of text. If you want to display multiple lines of text that can be edited, see Section 24, “The Text widget”.
Another solution to change the Tkinter label text is to change the text property of the label. The text of the label could be initiated with text="Text" and could also be updated by assigning the new value to the text key of the label object. We could also change the text property with the tk. Label.
This is just an example of using user-defined XBM as the bgstipple
of part of the text inside a Text
widget to simulate the squiggly line effect:
squiggly.xbm
, like below:A XBM with 10x20 pixels
Text
widget using the above XBM image file as bgstipple
in red color:# config a tag with squiggly.xbm as bgstipple in red color
textbox.tag_config("squiggly", bgstipple="@squiggly.xbm", background='red')
Text
widget:textbox.insert("end", "hello", "squiggly") # add squiggly line
Below is a sample code:
import tkinter as tk
root = tk.Tk()
textbox = tk.Text(root, width=30, height=10, font=('Courier New',12), spacing1=1)
textbox.pack()
# config a tag with squiggly.xbm as bgstipple in red color
textbox.tag_config("squiggly", bgstipple="@squiggly.xbm", background='red')
textbox.insert("end", "hello", "squiggly") # add squiggly line
textbox.insert("end", " world! ")
textbox.insert("end", "Python", "squiggly") # add squiggly line
textbox.insert("end", "\nthis is second line")
root.mainloop()
And the output:
Note that the height of the XBM image need to match the font size and spacing between lines.
I customized a frame,its layout(just an idea,it needs improvement):
When user input "Enter",it will generate a new entry and a new label(squiggly line widget).
If user type "Backspace" and this entry is null,it will delete both this entry and label(squiggly line widget).
When user type "Up" arrow,it will make previous entry widget focused.
....
Code:
import tkinter
class CustomText(tkinter.Frame):
def __init__(self,master):
super(CustomText,self).__init__()
self.last_line = 0
self.index_line = 0
self.master = master
self['background'] = 'white'
self.check_func = self.master.register(self.check)
first_line = tkinter.Entry(self,font=("",16),relief="flat",validate="key",validatecommand=(self.check_func,'%W','%P'))
first_line.pack(fill="x")
first_underline = tkinter.Label(self,background="white",fg="red",font=("",4))
first_underline.pack(anchor="nw")
self.widget_dict = {
first_line:first_underline # a dict which save the squiggly line widget(as a value) and entry widget(as a key)
}
# bind event:
first_line.bind("<Return>",self.create_new_line)
first_line.bind("<Up>",self.to_previous_line)
first_line.bind("<Down>",self.to_next_line)
first_line.bind("<FocusIn>",self.focused)
def focused(self,event): # when one entry widget is focused,change the index_line number
self.index_line = list(self.widget_dict.keys()).index(event.widget)
def create_new_line(self,event): # when user input enter,generate an entry and a label
self.index_line += 1
self.last_line += 1
new_line = tkinter.Entry(self,font=("",14),relief="flat",validate="key",validatecommand=(self.check_func,'%W','%P'))
new_line.pack(fill='x')
new_underline = tkinter.Label(self, background="white", fg="red", font=("", 4))
new_underline.pack(anchor="nw")
# also bind an event
new_line.bind("<Return>", self.create_new_line)
new_line.bind("<Up>",self.to_previous_line)
new_line.bind("<Down>",self.to_next_line)
new_line.bind("<FocusIn>",self.focused)
# the difference between the first line:when user delete all the words in this widget and he input "backspace" again, it will delete the entry and label widget,
new_line.bind("<BackSpace>",self.delete_this_line)
new_line.focus_set()
self.widget_dict[new_line] = new_underline
def to_next_line(self,event): # when user type "Down",go to the previous line
if self.index_line != self.last_line:
self.index_line += 1
to_widget = tuple(self.widget_dict.keys())[self.index_line]
to_widget.focus_set()
if event: # to the same index of next entry widget.
to_widget.icursor(event.widget.index("insert"))
def to_previous_line(self,event): # when user type "Up",go to the previous line
if self.index_line:
self.index_line -= 1 # the number of index minus 1
to_widget = tuple(self.widget_dict.keys())[self.index_line]
to_widget.focus_set()
if event:
to_widget.icursor(event.widget.index("insert"))
def delete_this_line(self,event):
if not event.widget.get():
self.last_line -= 1
self.widget_dict[event.widget].destroy() # delete it in visual
del self.widget_dict[event.widget] # delete reference in the self.widget_dict
event.widget.destroy()
del event.widget
self.to_previous_line(None)
def check(self,widget_str,input_str): # this is an error-check function
widget = self.nametowidget(widget_str) # convert the widgetname to a widget object
# an example
error_str = "abc"
if input_str == error_str: # now is to check the grammar
underline_widget = self.widget_dict[widget]
underline_widget['text'] = "〜"*len(error_str)*2 # add a squiggly line visually
return True
root = tkinter.Tk()
t = CustomText(root)
t.pack()
root.mainloop()
Example image(show the squiggly line when user input "abc"):
What needs to be improved:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With