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:
e1 loses focus and calls callback1
- Meanwhile, the cursor is placed in
e2 and e2 gets focus
- The msgbox window for
e1 takes focus and is ready to pop up just when...
e2 loses focus and calls callback2
- The msgbox window for
e2 pops up
- 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.