Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to have a red squiggly line appear under words in a Tkinter text widget without using canvas? (Like for misspelled words)

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:

enter image description here

If so where would I start?

like image 351
Jadon Erwin Avatar asked Apr 29 '20 16:04

Jadon Erwin


People also ask

What is the difference between text and entry in Tkinter?

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”.

How do I change text in Tkinter?

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.


2 Answers

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:

  • create a XBM image, for example squiggly.xbm, like below:

enter image description here

A XBM with 10x20 pixels

  • then you can config a tag in 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')
  • and apply the tag to the portion of text inside 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:

enter image description hereenter image description here

Note that the height of the XBM image need to match the font size and spacing between lines.

like image 93
acw1668 Avatar answered Sep 24 '22 02:09

acw1668


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"):

enter image description here


What needs to be improved:

  1. the line-height of label(squiggly line) should be smaller.(To make the squiggly line and entry widget closer)
  2. Actually,the label(squiggly line) could be image.(In my example,one character == two "~" characters)
  3. the function of checking.
  4. you could add two scrollbars.
like image 27
jizhihaoSAMA Avatar answered Sep 24 '22 02:09

jizhihaoSAMA