2

I would like to display a warning if the user does not input integers into an Entry, here is a minimal example:

import tkinter as tk
from tkinter import messagebox

class MainWindow(tk.Tk):
    """ The main window
    """
    def __init__(self):
        tk.Tk.__init__(self)
        self.configure_window()


    def configure_window(self):
        """ Configure the main window
        """
        self.geometry("600x400")
        self.title("Testing entry validate command")
        self.bind_all('<Control-w>', lambda e: self.destroy())
        var1 = tk.StringVar(value="0")
        vcmd = (self.register(self.validate_input))
        entry1 = tk.Entry(
            self,
            textvariable=var1,
            validate='all',
            validatecommand=(vcmd, '%P'))
        entry1.place(relx=0.5, rely=0.5, anchor=tk.CENTER)

    def validate_input(self, txt):
        """ Validate that the input only contains digits
        """
        print(txt)
        if str.isdigit(txt) or txt == "":
            return True
        messagebox.showwarning(
            "Alert", "Please enter a positive integer")
        return False


def main():
    window = MainWindow()
    window.mainloop()

main()

The problem is that the validate_input() method is not called after the warning message has been shown a first time. For example if the user enter "0", "1", "r" (three key strokes) into the entry, validate_input() is called for each key stroke, and for the last key stroke (the letter "r") the warning message box is shown. Next, if the user continues to type (after pressing "OK" in the warning message box) into the Entry, the validate_input() method is not called any more for the following key strokes. Expected behaviour would be that it is called for any key stroke independent of whether the message box has been shown or not.

What can be the problem?

Delrius Euphoria
  • 14,910
  • 3
  • 15
  • 46
Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174

3 Answers3

3

My guess is that this happens because you've set validate to "all". "all" encompasses validating on focus out. When the dialog pops up, the entry loses focus. This causes tkinter to attempt to do another round of validation in the middle of doing validation. That probably triggers validation to be turned off since you could get into an unescapable loop.

You need to either come up with a way to alert the user without losing focus on the entry widget, or you will need to restore the validation feature after the dialog is dismissed.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
2

Since this question does not have a correct answer, I cannot close it as a duplicate, but here is all you need:

def validate_input(self, txt):
    """ Validate that the input only contains digits
    """
    print(txt)
    if txt.isdigit() or txt == "":
        return True
    else:
        messagebox.showwarning("Alert", "Please enter a positive integer")
        self.entry1.config(validate='all',validatecommand=(self.vcmd, '%P')) # Set the validation back
        return False

Make sure to say self.entry1 and also self.vcmd:

self.vcmd = (self.register(self.validate_input))
self.entry1 = tk.Entry(self,textvariable=var1,validate='all',validatecommand=(self.vcmd, '%P'))
self.entry1.place(relx=0.5, rely=0.5, anchor=tk.CENTER)

Though I am not quite sure, why this is happening, but this fixes it. Looks like after messagebox the widget loses its validation and we have to set it back up once again.

Also like what Bryan said, if you change validate='all' to validate='key' it fixes it:

self.entry1 = tk.Entry(self,textvariable=var1,validate='key',validatecommand=(self.vcmd, '%P'))
Delrius Euphoria
  • 14,910
  • 3
  • 15
  • 46
2

Not the best solution but it works:

import tkinter as tk
from tkinter import messagebox

class MainWindow(tk.Tk):
    """ The main window
    """
    def __init__(self):
        tk.Tk.__init__(self)
        self.configure_window()


    def configure_window(self):
        """ Configure the main window
        """
        self.geometry("600x400")
        self.title("Testing entry validate command")
        self.bind_all("<Control-w>", lambda e: self.destroy())
        vcmd = (self.register(self.validate_input))
        entry1 = tk.Entry(self)
        entry1.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
        entry1.bind("<Key>", self.validate_input)

    def validate_input(self, event):
        """ Validate that the input only contains digits
        """
        if (event.state & 4) >> 2 == 1:
            # Ctrl+<event.keysym> was pressed
            if event.keysym == "c":
                # Ctrl+c pressed
                return None
            if event.keysym == "x":
                # Ctrl+x pressed
                return "break"
            if event.keysym == "v":
                # Ctrl+v pressed
                return "break"
        if not (event.char.isdigit() or event.char == ""):
            messagebox.showwarning(
                "Alert", "Please enter a positive integer")
            return "break"


def main():
    window = MainWindow()
    window.mainloop()

main()

When you return "break" from an event that event is blocked and isn't processed.

TheLizzard
  • 7,248
  • 2
  • 11
  • 31