Alexander Korovin
Alexander Korovin

Reputation: 1475

Interactively validating Entry widget content in tkinter (part 2 - change the properties of the entry object)

I'm trying to use the validation procedure from the answer here: Interactively validating Entry widget content in tkinter

I want to change the background color by checking the input type. Unfortunately, I can't pass the entry pointer to the validation function: vcmd = (self.register(self.onValidate), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W'). Thus, I tried this:

    self.entry = tk.Entry(self, validate="all")
    self.entry['validatecommand'] = self.onValidate2(self.entry)

But this only works once. Could you please explain me what is the best way to access to the object from which the validation function (or other one) is called and why my validatecommand usage only works once?

Here is the full code from the above link with some corrections:

import tkinter as tk  # python 3.x
# import Tkinter as tk # python 2.x

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        # valid percent substitutions (from the Tk entry man page)
        # note: you only have to register the ones you need; this
        # example registers them all for illustrative purposes
        #
        # %d = Type of action (1=insert, 0=delete, -1 for others)
        # %i = index of char string to be inserted/deleted, or -1
        # %P = value of the entry if the edit is allowed
        # %s = value of entry prior to editing
        # %S = the text string being inserted or deleted, if any
        # %v = the type of validation that is currently set
        # %V = the type of validation that triggered the callback
        #      (key, focusin, focusout, forced)
        # %W = the tk name of the widget

        vcmd = (self.register(self.onValidate),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
 #       self.entry = tk.Entry(self, validate="key", validatecommand=vcmd)
        self.entry = tk.Entry(self, validate="all")
#        self.entry['validatecommand'] = vcmd
        self.entry['validatecommand'] = self.onValidate2(self.entry)
        self.text = tk.Text(self, height=10, width=40)
        self.entry.pack(side="top", fill="x")
        self.text.pack(side="bottom", fill="both", expand=True)

def onValidate(self, d, i, P, s, S, v, V, W):
        self.text.delete("1.0", "end")
        self.text.insert("end","OnValidate:\n")
        self.text.insert("end","d='%s'\n" % d)
        self.text.insert("end","i='%s'\n" % i)
        self.text.insert("end","P='%s'\n" % P)
        self.text.insert("end","s='%s'\n" % s)
        self.text.insert("end","S='%s'\n" % S)
        self.text.insert("end","v='%s'\n" % v)
        self.text.insert("end","V='%s'\n" % V)
        self.text.insert("end","W='%s'\n" % W)
        self.text.insert("end","W='%s'\n" % W)
        W.config({"background": "Red"})

        # Disallow anything but lowercase letters
        if S == S.lower():
            return True
        else:
            self.bell()
            return False


    def onValidate2(self, entry):
        try:
            entry.config({"background": "White"})
            value = int(entry.get())
            print("Int Value=",value)
#                return int(value)
        except ValueError:
            entry.config({"background": "Red"})

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()

Upvotes: 2

Views: 708

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 386342

I can't pass the entry pointer to the validation function

You can pass the name of the entry widget, and use tkinter's nametowidget method to convert that name into the instance of the widget.

Could you please explain ... why my validatecommand usage only works once?

You are improperly configuring the validate command. Consider this code:

self.entry['validatecommand'] = self.onValidate2(self.entry)

The above code is functionally identical to this:

result = self.onValidate2(self.entry)
self.entry['validatecommand'] = result

In other words, you are immediately calling your validation function and then setting the validatecommand option to None. the validationcommand option must be set to a callable.


Here is a working example of what you are trying to achieve. Normally the validate command must return True for a valid entry and False for an invalid entry, but I'm guessing you actually want to allow invalid entries and instead just want to turn the background red.

This example creates multiple entry widgets so you can see that the single validation function works for multiple widgets, by passing the name of the widget to the validation function.

import tkinter as tk

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        vcmd = self.register(self.onValidate)

        for i in range(4):
            entry = tk.Entry(self, validate="all")
            entry.configure(validatecommand=(vcmd, "%W", "%P"))
            entry.pack(side="top", fill="x")

    def onValidate(self, entry_name, new_value):
        entry = self.nametowidget(entry_name)
        entry.configure(background="white")
        try:
            int(new_value)
        except ValueError:
            entry.configure(background="red")
        return True

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()

Upvotes: 2

Related Questions