How to I create an event for user entry?

2019-08-19 02:07发布

问题:

As part of my program I am asking the user for their name and their class (high school class). I am using a text entry function which is successfully accepting the input but I need help on validation: I only want the 'Enter' button to become active once the user has actually started typing as otherwise the user will press the 'Enter' button and deactivate it. Also, I would like to make sure that when they enter their name the program will only accept letters and no numbers at all. For the second entry (school class/tutor class) the user will enter something like 6A1 which is their class. There are about 10 different senior classes in my school so how can I either validate the entry to accept only 1 of these 10 classes or perhaps a drop down menu? Help would be greatly appreciated :)

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)

        enter_name = Entry(self)
        enter_name.pack()
        enter_name.focus_set()


        def callback():
            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 = Entry(self)
            tutor.pack()
            tutor.focus_set()
            Enter_0.config(state="disabled")

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


        Enter_0 = Button(self, text="Enter", width=10, command=callback)
        Enter_0.pack()

回答1:

The way to do what you literally asked, "the 'Enter' button to become active once the user has actually started typing" is to bind the change or keypress event on the enter_name, and activate Enter_0 once it's triggered.

But that's probably not what you actually want. If the user enters some text and then deletes it, wouldn't it be nicer if the button disabled again? And if the user pastes some text without typing anything, shouldn't that enable the button?

To do that, you want one of two things: validation, or variable tracing.


Before we get into it, you're almost certainly going to want to store the Enter_0 button as an attribute on self instead of creating and re-creating new buttons on top of each other. So, I'll do that in my example.


Validation, although it's very badly documented in Tkinter and a bit clumsy to use, is very powerful, and the obvious fit for what you're trying to do—validate text:

def __init__(self, parent):

    # existing stuff

    vcmd = self.root.register(self.validate)
    enter_name = Entry(self, validate='key', validatecommand=(vcmd, '%P'))

    # existing stuff

    self.Enter_0 = Button(self, text="Enter", width=10, command=callback)
    self.Enter_0.pack()

def validate(self, P):
    self.Enter_0.config(state=(NORMAL if P else DISABLED))
    return True

This probably looks like unreadable magic, and the Tkinter docs give you no guidance. But the Tk docs for validatecommand show what it means:

  • The key bit means that the command "will be called when the entry is edited".
  • The %P means the "value of the entry if the edit is allowed". You can stick as many of those % strings as you want into your vcmd, and they will be passed as arguments to your validate method. So, you could pass (vcmd, '%s', '%P', '%v') and then define validate(self, s, P, v).
  • You can do anything you want in the function, and then return True or False to accept or reject the change (or return None to stop calling your validation function).

Anyway, now, if the user attempts to edit the entry in any way, then the Enter_0 button will be set to NORMAL if their edit would give you a non-empty string, DISABLED otherwise.


Variable tracing is conceptually a lot clunkier, but in practice often simpler. It's also not completely documented, but at least it's somewhat documented.

The idea is to create a StringVar, attach it to the Entry, and put a "write trace" on it, which is a function that gets called every time the variable is updated (which happens every time the Entry changes contents). Like this:

def __init__(self, parent):

    # existing stuff

    name_var = StringVar()
    def validate_enter():
        self.Enter_0.config(state=(NORMAL if var.get() else DISABLED))
    name_var.trace('w', lambda name, index, mode: validate_enter)
    enter_name = Entry(self, textvariable=name_var)

    # existing stuff--and again, do the self.Enter_0 change