Search code examples
pythonpython-3.xtkintertkinter-canvastkinter-entry

How do I make a tkinter message disappear if the entry in Tkinter Widget has been corrected?


I have created a button which if pressed, directs to the check function which is sort of a validation check for an entry widget for users.

If the first name that the user enters contains anything apart from letters, it should give an invalid check message at coordinate (200, 500) and if last name has an error like that, there should be a message at (200, 700) -- else the screen should proceed to the next screen.

However, if I enter one wrong entry and one right entry, although the message appears at the right place - when I now make the wrong entry right and right entry wrong, instead of the message disappearing from one position, it stays as it is and another message appears at another position (2 messages now, even thought just one entry is wrong)

I am sorry if that is confusing but in simple words, my goal is that the message should only appear on the coordinates where the entry has gone wrong and disappear if the entry is corrected.

How should I alter my code to ensure this?

def check(FName_Entry, LName_Entry):


if not FName_Entry.get().isalpha():
    errormsg = Message(root, text = 'Invalid entry', anchor = CENTER)
    canvas.create_window(200, 500, anchor=NW, window = errormsg)
    #messagebox.showerror('Only letters', 'Only letters are allowed!')

if not LName_Entry.get().isalpha():
    errormsg2 = Message(root, text='Invalid entry', anchor=CENTER)
    canvas.create_window(200, 700, anchor=NW, window=errormsg2)

if FName_Entry.get().isalpha() and LName_Entry.get().isalpha():
    canvas.delete("all")

. . . .

Next_button = Button(root, text="Next", anchor=CENTER, command=lambda: check(FName_Entry, LName_Entry))
    Next_button.configure(width=10, bg="black", fg="blue", border=10)
    canvas.create_window(920, 450, anchor=NW, window=Next_button)

Solution

  • I'm assuming that this is the part that you get to after selecting whether you are a user or a driver. If we stick with the example that I gave you in your other post:

    new config.py

    from dataclasses import dataclass, asdict
    
    
    #to make things more complicated and force you to get better
    #create a dataclass of default properties
    @dataclass
    class Button_dc:
        bg:    str = "white"
        fg:    str = "gray20"
        font:  str = 'Calibri 14 bold'
        border:int = 10
    
        
    #make a dict instance of the dataclass    
    DefaultButton = asdict(Button_dc())
    #make a slightly different instance
    UserRoleButton = asdict(Button_dc(font='Calibri 24 bold'))
    
    
    @dataclass
    class Label_dc:
        bg:    str = "steel blue"
        fg:    str = "gray20"
        font:  str = 'Calibri 14 bold'
        
    DefaultLabel = asdict(Label_dc())
    
    
    @dataclass
    class Entry_dc:
        bg:    str = "white"
        fg:    str = "gray20"
        font:  str = 'Calibri 14 bold'
        width: int = 30
        
    DefaultEntry = asdict(Entry_dc())
    
    @dataclass
    class Message_dc:
        bg:    str = "red"
        fg:    str = "white"
        font:  str = 'Calibri 14 bold'
        width: int = 150
        
    DefaultMessage = asdict(Message_dc())
    

    In main.py select the entire user_form method and paste the entire below in its place

        def user_form(self, type, fname=None, lname=None):
            self.canvas.delete("all")
            
            self.canvas.create_window(10, 10, anchor='nw', window=tk.Label(self, text="First Name:", **cfg.DefaultLabel))
            
            fname_ent = tk.Entry(self, **cfg.DefaultEntry)
            if fname != None:
                fname_ent.insert('end', fname)
            self.canvas.create_window(120, 10, anchor='nw', window=fname_ent)
            
            self.canvas.create_window(10, 40, anchor='nw', window=tk.Label(self, text="Last Name:", **cfg.DefaultLabel))
            
            lname_ent = tk.Entry(self, **cfg.DefaultEntry)
            if lname != None:
                lname_ent.insert('end', lname)
            self.canvas.create_window(120, 40, anchor='nw', window=lname_ent)
            
            #don't send the entire Entry to the check ~ only the data you want to check
            form_next_btn = tk.Button(self, text="next", command=lambda: self.check(type, fname_ent.get(), lname_ent.get()), **cfg.DefaultButton)
            self.fnb = self.canvas.create_window(500, 340, anchor='nw', window=form_next_btn)
            
        def check(self, type, fname, lname):
            self.canvas.delete(self.fnb)
            
            f = fname.isalpha()
            l = lname.isalpha()
            
            if f and l:
               self.process(type, fname, lname)
               return
            
            if not f:
                errormsg = tk.Message(self, text='Invalid entry for first name', **cfg.DefaultMessage)
                self.canvas.create_window(10, 90, anchor='nw', window=errormsg)
                
            if not l:
                errormsg2 = tk.Message(self, text='Invalid entry for last name', **cfg.DefaultMessage)
                self.canvas.create_window(10 if f else 170, 90, anchor='nw', window=errormsg2)
                
            back_btn = tk.Button(self, text="back", command=lambda: self.user_form(type, fname if f else None, lname if l else None), **cfg.DefaultButton)
            self.canvas.create_window(500, 340, anchor='nw', window=back_btn)
            
        def process(self, type, fname, lname):
            self.canvas.delete("all")
            
            if type is Role.User:
                print(f'{fname} {lname} is a user')
            elif type is Role.Driver:
                print(f'{fname} {lname} is a driver')
    

    aside: Keep in mind that I am not trying, at all, to make your app pretty. I'm just showing you how to make it work. I'm also not writing my best code. I would do all of this completely different. I'm mushing around with your canvas method and giving you something with a modicum of sanity to it. It seems to me that the entire reason you are using canvas is just so you can have a rounded rectangle. It hardly seems worth it, but that isn't my decision to make.

    However, these concepts do not suck and can be very powerful in the proper context. Lets take the dataclasses, for instance. Imagine if you actually wanted to do the below. You end up with a fully customized Text widget and a very simple way to change only what you need to change while maintaining everything else you assigned. The dataclasses that I wrote for you are little example concepts. You can bring those concepts much further.

    @dataclass 
    class Text_dc:
        background:         str         = Theme.Topcoat     # main element
        foreground:         str         = Theme.Flat
        borderwidth:        int         = 0
        selectbackground:   str         = Theme.Primer      # selected text
        selectforeground:   str         = Theme.Accent
        selectborderwidth:  int         = 0                 # doesn't seem to do anything ~ supposed to be a border on selected text
        insertbackground:   str         = Theme.Hilight     # caret
        insertborderwidth:  int         = 0                 #   border
        insertofftime:      int         = 300               #   blink off millis
        insertontime:       int         = 600               #   blink on millis
        insertwidth:        int         = 2                 #   width
        highlightbackground:int         = Theme.Topcoat     # inner border that is activated when the widget gets focus
        highlightcolor:     int         = Theme.Topcoat     #   color
        highlightthickness: int         = 0                 #   thickness
        cursor:             str         = 'xterm'
        exportselection:    int         = 1
        font:               str         = 'calibri 14'
        width:              int         = 16                # characters    ~ often this is ignored as Text gets stretched to fill its parent
        height:             int         = 8                 # lines         ~ "               "               "               "               "
        padx:               int         = 2
        pady:               int         = 0
        relief:             str         = 'flat'
        wrap:               str         = 'word'            # "none", 'word'
        spacing1:           int         = 0                 # space above every line
        spacing2:           int         = 0                 # space between every wrapped line
        spacing3:           int         = 0                 # space after return from wrap (paragraph)
        state:              str         = 'normal'          # NORMAL=Read/Write | DISABLED=ReadOnly
        tabs:               str         = '4c'
        takefocus:          int         = 1
        undo:               bool        = True
        xscrollcommand:     Callable    = None
        yscrollcommand:     Callable    = None
    
    #common difference
    NoWrap      = asdict(Text_dc(**{'wrap':'none'}))
    NWReadOnly  = asdict(Text_dc(**{'wrap':'none','state':'disabled'}))
    ReadOnly    = asdict(Text_dc(**{'state':'disabled'}))
    DefaultText = asdict(Text_dc())
    

    You can use that concept in a different way. Let's assume that you wanted a completely custom Text widget and you intended to do more than just restyle it. The below is a quick and dirty example

    class ScriptEditor(tk.Text):
        def __init__(self, master, **kwargs):
            tk.Text.__init__(self, master, **asdict(Text_dc(font='Consolas 14', **kwargs)))
    
        def syntax_highlight(self):
            pass
    
    
    #all the other **kwargs are defaulted internally, 
    #but we want to adjust the size for this instance
    editor = ScriptEditor(root, width=120, height=80)