Aman Khanna
Aman Khanna

Reputation: 375

How do I prevent focus change if an entry is invalid?

I want if one field has invalid entry, message to be displayed on moving out of that field and focus should remain on that field. But in following code validation of next field is also triggered when moving out of first field. I have commented set focus, otherwise, it gets into an infinite loop.

from tkinter import *
from tkinter import ttk, messagebox

root = Tk()

def callback1():
    if (len(e1.get()) <4):
        messagebox.showinfo("error", "Field 1 length < 4")
        #e1.focus_set()
        return False
    else:
        return True
    
def callback2():
    if (len(e2.get()) <4):
        messagebox.showinfo("error", "Field 2 length < 4")
        #e2.focus_set()
        return False
    else:
        return True

e1 = Entry(root, validate="focusout", validatecommand=callback1)
e1.grid()
e2 = Entry(root,  validate="focusout", validatecommand=callback2)
e2.grid()
root.mainloop()

Upvotes: 5

Views: 328

Answers (1)

Sriram Srinivasan
Sriram Srinivasan

Reputation: 700

When you place your cursor in e1, type something which does not satisfy the validatecommand condition and then try to place your cursor in e2, the following sequence of events takes place:

  1. e1 loses focus and calls callback1
  2. Meanwhile, the cursor is placed in e2 and e2 gets focus
  3. The msgbox window for e1 takes focus and is ready to pop up just when...
  4. e2 loses focus and calls callback2
  5. The msgbox window for e2 pops up
  6. This is followed by the pop up for e1 which was waiting in the queue

The root cause for the problem is that the messagebox window takes focus, which in turn triggers the other entry box. This approach seems to be highly fragile due to the strong interplay of events.

Notice that if you just print the information to the terminal, everything works perfectly as the terminal doesn't take focus.

So, I would recommend you display the information using an alternate method where the widget showing the information won't steal focus. One option is to display the information using a label.


Now coming to your second problem, if you want the entry to retain focus if the entered text is not valid, you could use a global variable (hanging) to keep track of whether the user is in the process of filling an entry successfully.

If the user is in the process of filling an entry, he/she will not be able to place the cursor in the other entry because FocusIn1 and FocusIn2 return "break" when hanging equals True.

You can replace the print statements in the below working code using a label.


Working Code:

from tkinter import *
from tkinter import ttk, messagebox

root = Tk()
hanging = False #True implies that the user is in the process of filling an entry successfully

def onFocusOut1():
   global hanging
   hanging = True
   if (len(e1.get()) <4):
       print("error", "Field 1 length < 4")
       e1.focus_set()
       return False
   else:
      hanging = False
      return True
    
def onFocusOut2():
   global hanging
   hanging = True
   if (len(e2.get()) <4):
       print("error", "Field 2 length < 4")
       e2.focus_set()
       return False
   else:
       hanging = False
       return True

def onFocusIn1():
    if hanging:
       return "break"
    e1.configure(validate="focusout", validatecommand=onFocusOut1)

def onFocusIn2():
    if hanging:
       return "break"
    e2.configure(validate="focusout", validatecommand=onFocusOut2)

e1 = Entry(root)
e1.grid()
e2 = Entry(root)
e2.grid()

#Binding both the entries to FocusIn
e1.bind("<FocusIn>", lambda e: onFocusIn1())
e2.bind("<FocusIn>", lambda e: onFocusIn2())

root.mainloop()

PS: It turns out that you can actually use messagebox.showinfo itself in place of print in the working code. The first problem got solved automatically along with the second one. So, this gives the complete solution to your problem.

Upvotes: 1

Related Questions