'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()
Solution 1:[1]
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:
e1loses focus and callscallback1- Meanwhile, the cursor is placed in
e2ande2gets focus - The msgbox window for
e1takes focus and is ready to pop up just when... e2loses focus and callscallback2- The msgbox window for
e2pops up - This is followed by the pop up for
e1which 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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
