1

I want the user to be able to enter an integer value in the Spinbox widget. If the value entered is not an integer or is an integer outside the Spinbox limits, as soon as the Spinbox loses focus, the value in the Spinbox's content must revert to a default value.

In the example code, I use the Entry widget just for the Spinbox can lose focus.

If the user comes back to Spinbox to enter a new value, his entry is not validated. I confirm Malcolm's remark in Interactively validating Entry widget content in tkinter that the validatecommand=command feature gets cleared as soon as this command updates the widget's value.

Is there a way to get the value entered in the Spinbox repeatedly validated and not just once?

from tkinter import *


class GUI:

    def __init__(self):
        # root window of the whole program
        self.root = Tk()
        self.root.title('Validate Spinbox')

        # registering validate and invalid commands
        validate_cmd = (self.root.register(self.validate), '%P')
        invalid_cmd = (self.root.register(self.invalid))

        # creating a Label
        items_lbl = Label(self.root, text="# of items (5-10):")
        items_lbl.grid(row=0, column=0)

        # creating a Spinbox widget
        self.items_var = StringVar()
        self.items_var.set(7)
        items_count = Spinbox(self.root, textvariable=self.items_var,
                              from_=5, to=10, width=4, validate='focusout',
                              validatecommand=validate_cmd,
                              invalidcommand=invalid_cmd)
        items_count.grid(row=0, column=1)

        # creating an Entry widget
        self.entry_var = StringVar()
        self.entry_var.set("Input some text here")
        text_entry = Entry(self.root, textvariable=self.entry_var)
        text_entry.grid(row=1, column=0)

    def validate(self, entry):
        try:
            value = int(entry)
            valid = value in range(5, 11)
        except ValueError:
            valid = False
        if not valid:
            self.root.bell()
        return valid

    def invalid(self):
        self.items_var.set(7)


if __name__ == '__main__':
    main_window = GUI()
    mainloop()
nkana
  • 13
  • 5
  • 1
    `validatecommand` stays OK, it's the `validate` parameter bound to the `focusout` event that get reset – PRMoureu Oct 21 '17 at 09:13
  • @PRMoureu: Thank you for your response!!! Is there a way not to reset the **validate** parameter? Most preferably: As I am new to Python, I would be greatly obliged if you could amend my code. – nkana Oct 21 '17 at 11:28

1 Answers1

1

I found a great explanation here (in the last paragraph of the chapter Validation):

http://stupidpythonideas.blogspot.fr/2013/12/tkinter-validation.html

If your validatecommand (or invalidcommand) modifies the Entry directly or indirectly (e.g., by calling set on its StringVar), the validation will get disabled as soon as your function returns. (This is how Tk prevents an infinite loop of validate triggering another validate.) You have to turn it back on (by calling config). But you can't do that from inside the function, because it gets disabled after your function returns.

But you need to apply some changes to be able to use this trick.

You need to make the Spinbox an instance attribute, with self :

self.items_count = Spinbox(self.root, textvariable=self.items_var,
                      from_=5, to=10, width=4, validate='focusout',
                      validatecommand=validate_cmd,
                      invalidcommand=invalid_cmd)
self.items_count.grid(row=0, column=1)

And then you can call self.items_count.after_idle(...) inside the validate method :

def validate(self, entry):
    try:
        value = int(entry)
        valid = value in range(5, 11)
    except ValueError:
        valid = False
    if not valid:
        self.root.bell()
        self.items_count.after_idle(lambda: self.items_count.config(validate='focusout'))
    return valid
PRMoureu
  • 12,817
  • 6
  • 38
  • 48