質問

As part of my program I ask the user for their name and class (high school class). Once the user presses 'Enter' after typing their name the button is disabled and the 'tutor' field appears. However, the user is in essence able to submit their name even if they haven't typed anything. I only want the 'Enter' button to be active once the user has started typing.

What I have done below doesn't seem to work :(

Also, my input validation doesn't work - know why?

class Enter_Name_Window(tk.Toplevel):
    '''A simple instruction window'''
    def __init__(self, parent):
        tk.Toplevel.__init__(self, parent)
        self.text = tk.Label(self, width=40, height=2, text= "Please enter your name and class." )
        self.text.pack(side="top", fill="both", expand=True)

        name_var = StringVar()
        def validate_enter_0():
            self.Enter_0.config(state=(NORMAL if name_var.get() else DISABLED))
            print("validate enter worked")

        name_var.trace('w', lambda name, index, mode: validate_enter_0)
        enter_name = Entry(self, textvariable=name_var)
        enter_name.pack()
        enter_name.focus_set()


        def callback():
            if len(name_var) > 10 or any(l not in string.ascii_letters for l in name_var):
                print("Input validation worked")

            self.display_name = tk.Label(self, width=40, height=2, text = "Now please enter your tutor group.")
            self.display_name.pack(side="top", fill="both", expand=True)


            tutor_var = StringVar()
            def validate_enter_2():
                self.Enter_0_2.config(state=(NORMAL if tutor_var.get() else DISABLED))
                print("validate enter worked")
            tutor_var.trace('w', lambda name, index, mode: validate_enter_0_2)
            tutor = Entry(self, textvariable=tutor_var)
            tutor.pack()
            tutor.focus_set()

            self.Enter_0.config(state="disabled")

            self.Enter_0_2 = Button(self, text="Enter", width=10, command=self.destroy)
            self.Enter_0_2.pack()


        self.Enter_0 = Button(self, text="Enter", width=10, command=callback)
        self.Enter_0.pack()
役に立ちましたか?

解決

The first obvious problem is this line:

tutor_var.trace('w', lambda name, index, mode: validate_enter_0_2)

You've created a function that takes three variables, and returns the validate_enter_0_2 function as a function object. That doesn't do any good.

You want to create a function that calls the validate_enter_0_2 function. Like this:

tutor_var.trace('w', lambda name, index, mode: validate_enter_0_2())

You have the exact same problem with name_var and will also need to fix it there, of course.


On top of that, you don't actually have a function named validate_enter_0_2 to call, because you defined it as validate_enter_2. This means your validation function just raises a NameError instead of doing useful. Or, if you have a validate_enter_2 function defined somewhere else in your code, it calls the wrong function. (This is one reason that cryptic names like enter_0_2 and enter_2 are not a good thing.)


There's at least one other problem with your code: You're repeatedly trying to use name_var, which is a StringVar object, as if it were a string. You can't do that. And if you actually look at the console output, Tkinter will tell you this, with tracebacks like this:

Exception in Tkinter callback
Traceback (most recent call last):
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
    return self.func(*args)
  File "tkval2.py", line 25, in callback
    if len(name_var) > 10 or any(l not in string.ascii_letters for l in name_var):
AttributeError: StringVar instance has no attribute '__len__'

And that exception is happening before you get a chance to create the new Entry.

To fix that, you need to call get on the StringVar whenever you want to get its value, like this:

if len(name_var.get()) > 10 or any(l not in string.ascii_letters for l in name_var.get())

Finally, as I explained in the answer to your other question, your trace validator isn't going to get called until something changes. This means you will either need to call it explicitly, or explicitly name_var.set(''), or just start the button off disabled. As written, it will start off enabled, and only disable if you type something and then erase it.


I'm not sure whether those are the only problems with your code, but all of them will certainly prevent your validation from working as expected. So, you need to fix all of them, and any other errors in your code, before it will work.


From your comments:

I am however wondering how to create a pop up message displaying an error…

When do you want to do that? What condition do you want to check, and when do you want to check it?

At any rate, as in most GUIs, the way to "pop up" anything like this is a dialog. Dialog Windows in the Tkinter book explains everything you need to know. But you don't need to copy-paste all that code, or write it from scratch; the stdlib comes with Tkinter helper modules that do most of the work for you. In your case, you probably just want tkMessageBox.

… and something that forces the user to re enter their name

Force them how? Just erasing the existing Entry contents would leave them with an empty box to fill in, which would also disable the button. Is that what you want?

Anyway, guessing at what you want, it could look something like this:

def callback():
    if len(name_var.get()) > 10:
        tkMessageBox.showerror("Bad name", "Your name is too long. What's wrong with good American names like Joe?")
        name_var.set('')
        return
    # the rest of your code here

In the callback function (called when they click the button after typing their name), instead of just checking some condition and printing something out, I check a condition and pop up an error dialog, clear out the existing name, and return early instead of creating the second half of the form. I didn't handle your other condition (any non-ASCII letters), but it should be obvious how to add that.

However, validation like this might be better done through actual validation—instead of making them wait until they click the button, catch it as soon as they try to type the 11th character, or a space or accented character, or whatever else you don't like. Then you can pop up a message box, and either disable the button until they fix it, reject/undo the change (which is easier with a validatecommand function than with a trace function, as shown in my answer to your previous question).

One last thing: Instead of a message box, it may be better to just embed the error as, say, a Label that appears in the form itself (maybe with the error description in red, with a big flag icon). This is common in web apps and in more modern GUIs because it provides more immediate feedback, and is less obtrusive to the user flow.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top