2

I'm creating a GUI using Tkinter that is used to collect information from a user. I need to validate:

  • First and Last name only contain letters, apostrophes, and dashes
  • Address
  • Phone number has the correct number of digits
  • Valid birthday (Feb 31 doesn't exist and birth year between 1900 and 2014)
  • Email address contains '@' and '.'

Here is my code:

from tkinter import *
import datetime

class tkwindow:

def __init__(self):

    window = Tk() # Create window
    window.title("Contact Form") # Give window a title

    menubar = Menu(window) # Create menu bar
    window.config(menu = menubar) # Display menu bar

    '''Using the pulldown menu allows easier access to the menu items instead of using a pop-up menu '''

    # Create pulldown menu and add to menu bar
    messagesMenu = Menu(menubar, tearoff = 0)
    menubar.add_cascade(label = "Messages", menu = messagesMenu)
    messagesMenu.add_command(label = "Send a Birthday Greeting", command = self.bdayGreeting)
    messagesMenu.add_command(label = "Print Address", command = self.printAddress)

    # Create another menu option
    endMenu = Menu(menubar, tearoff = 0)
    menubar.add_cascade(label = "End", menu = endMenu)
    endMenu.add_command(label = "Reset Form", command = self.resetForm)
    endMenu.add_command(label = "Exit Program", command = window.quit)

    # Using Label widget
    labelFirst = Label(window, text = "First Name: ").grid(row = 1, column = 1, sticky = E)
    labelLast = Label(window, text = "Last Name: ").grid(row = 2, column = 1, sticky = E)
    labelAddress = Label(window, text = "Address: ").grid(row = 3, column = 1, sticky = E)
    labelPhone = Label(window, text = "Phone Number (8005551234): ").grid(row = 4, column = 1, sticky = E)
    labelBday = Label(window, text = "Birthday (MM/DD/YYYY): ").grid(row = 5, column = 1, sticky = E)
    labelEmail = Label(window, text = "Email Address (user@domain.com): ").grid(row = 6, column = 1, sticky = E)

    # Using Entry widget
    self.firstName = StringVar()
    entryFirst = Entry(window, textvariable = self.firstName, justify = LEFT).grid(row = 1, column = 2, sticky = W)

    self.lastName = StringVar()
    entryLast = Entry(window, textvariable = self.lastName, justify = LEFT).grid(row = 2, column = 2, sticky = W)

    self.address = StringVar()
    entryAddress = Entry(window, textvariable = self.address, justify = LEFT).grid(row = 3, column = 2, sticky = W)

    self.phone = StringVar()
    entryPhone = Entry(window, textvariable = self.phone, justify = LEFT).grid(row = 4, column = 2, sticky = W)

    self.bday = StringVar()
    entryBday = Entry(window, textvariable = self.bday, justify = LEFT).grid(row = 5, column = 2, sticky = W)

    self.email = StringVar()
    entryEmail = Entry(window, textvariable = self.email, justify = LEFT).grid(row = 6, column = 2, sticky = W)

    self.errorLblFirst = Label(window, fg = "red")
    self.errorLblFirst.grid(row = 1, column = 3)

    self.errorLblLast = Label(window, fg = "red")
    self.errorLblLast.grid(row = 2, column = 3)

    self.errorLblAddress = Label(window, fg = "red")
    self.errorLblAddress.grid(row = 3, column = 3)

    self.errorLblPhone = Label(window, fg = "red")
    self.errorLblPhone.grid(row = 4, column = 3)

    self.errorLblBday = Label(window, fg = "red")
    self.errorLblBday.grid(row = 5, column = 3)

    self.errorLblEmail = Label(window, fg = "red")
    self.errorLblEmail.grid(row = 6, column = 3)

    # Using Button widget
    buttonSubmit = Button(window, text = "Submit", command = self.submit).grid(row = 7, column = 2, sticky = E)

    window.mainloop()

def resetForm(self):

    self.firstName.set('')
    self.errorLblFirst["text"] = ''
    self.lastName.set('')
    self.errorLblLast["text"] = ''
    self.address.set('')
    self.errorLblAddress["text"] = ''
    self.phone.set('')
    self.errorLblPhone["text"] = ''
    self.bday.set('')
    self.errorLblBday["text"] = ''
    self.email.set('')
    self.errorLblEmail["text"] = ''

def validFirst(self):

    for letter in self.firstName.get():
        if not letter.isalpha() and letter not in "'-":
            self.errorLblFirst["text"] = " * Letters, apostrophes('), and hypens(-) only"
            return False

    return True

def validLast(self):

    for letter in self.lastName.get():
        if not letter.isalpha() and letter not in "'-":
            self.errorLblLast["text"] = " * Letters, apostrophes('), and hypens(-) only"
            return False

    return True

def validAddress(self):

    for letter in self.address.get():
        if not letter.isalnum() and letter not in "'- .,":
            self.errorLblAddress["text"] = " * No special characters"
            return False

    return True

def validPhone(self):

    D = 0

    for number in self.phone.get():
        if number.isdigit():
            D += 1
        if D == 10:
            return True
        else:
            self.errorLblPhone["text"] = " * Must be 10-digit phone number without spaces"
            return False

    return True

def validBday(self):

    '''try:
        valid_date = datetime.datetime.strptime(str(self.bday), '%m/%d/%Y')
    except ValueError:
        print('Invalid date!')'''
    return True

def validEmail(self):

    for letter in self.email.get():
        if not letter.isalnum() and letter not in "@.":
            self.errorLblEmail["text"] = " * Must have @ and ."
            return False

    return True

def bdayGreeting(self):
    if self.validBday() and self.validFirst() == True:
        print("Happy Birthday" + self.firstName.get() + "\n" + self.bday.get())

def printAddress(self):
    if self.validFirst() and self.validLast() and self.validAddress() == True:
        print(self.firstName.get() + " " + self.lastName.get() + "\n" + self.address.get())


def submit(self):
    self.validFirst()
    self.validLast()
    self.validAddress()
    self.validPhone()
    self.validBday()
    self.validEmail()

tkwindow()

I have a couple questions.

  1. Starting from def validFirst(self), how do I validate the different entry fields? I keep getting NameError: name "asdf" is not defined, SyntaxError: unexpected EOF while parsing, and TypeError: 'int' object is not subscriptable when I modify the code and I'm still stuck on validFirst(self).
  2. I have a set of labels in column 3 reserved for error messages: errorLblFirst = Label(window, text = " ", fg = "red").grid(row = 1, column = 3). Is it possible to set it to " * Invalid Entry", fg = "red" when the validation for that entry fails? Is there another way to get error messages to show?

Thanks in advance for the input!

Edit: I updated to my latest code. Most of the validation works now except for validBday and validEmail if you could check it out.

annabananana7
  • 181
  • 7
  • 18
  • Please pick one version of the code and provide the *full error traceback* you get when running it. Ideally, please reduce your code to a [minimal working example](http://sscce.org) that shows the problem. You can certainly change the label text - but bear [this](http://stackoverflow.com/a/23625549/3001761) in mind. – jonrsharpe Jun 06 '14 at 16:04
  • have you ever considered using [validatecommand](http://stackoverflow.com/questions/4140437/python-tkinter-interactively-validating-entry-widget-content/4140988#4140988) feature? – Lafexlos Jun 06 '14 at 16:51
  • @Lafexlos no, that's not in my textbook =\ how do I implement it? – annabananana7 Jun 06 '14 at 17:21
  • If you check the linked answer in my first comment, that is kind of documentation for it. This is the tcl man page. http://www.tcl.tk/man/tcl8.5/TkCmd/entry.htm – Lafexlos Jun 06 '14 at 18:06
  • @Lafexlos is it not possible to just use for each and if-else loops to validate? I'm using self.object.get/set so maybe there are errors in my syntax? – annabananana7 Jun 06 '14 at 18:23

1 Answers1

3
errorLblFirst = Label(...).grid(...)

This way you assign result of grid() to errorLblFirst but grid() always returns None

Always do it this way

errorLblFirst = Label(...)
errorLblFirst.grid(...)

And now you can do (for example)

errorLblFirst["text"] = " * Invalid Entry"
errorLblFirst["fg"] = "red"

EDIT:

You forgot len() in for i in range(first): in validFirst() And you don't need eval(). (You got the same problem in validPhone())

F = 0

fName = True

first = self.firstName.get() # without eval()

for i in range(len(first)): # len()
    if first[i].isdigit():
        F += 1
    if F > 0:
        return False
    else:
       return fName

return fName

but you could do this shorter

for letter in self.firstName.get():
    if letter.isdigit():
        return False

return True

if you need only letters (no space, commas, etc.) you could do this

return self.firstName.get().isalpha()

EDIT:

I see First Name can have apostrophes, and dashes

for letter in self.firstName.get():
    if not letter.isalpha() and letter not in "'-":
        return False

return True

EDIT: problem with self.phone and StringVar

self.phone is StringVar() - not string but StringVar() has .get() to get string

for number in range(len( self.phone.get() )): 

By the way: you can do for loop more pythonic - without range(len())

for char in self.phone.get(): 
    if char.isdigit():     

EDIT: problem with validEmail() and validBday()

Email validation needs more if.

For example you could add

email = self.email.get()
if '@' not in email and '.' not in email::
    print( 'email needs @ and . at the same time')

but '.@' will pass this test :/

Real email validation is more complicated.

See valid email examples: https://fightingforalostcause.net/content/misc/2006/compare-email-regex.php


self.bday is StringVar - use self.bday.get() in place of str(self.bday)


EDIT:

def validPhone(self):

    D = 0

    for number in self.phone.get():
        if number.isdigit():
            D += 1

    # outside 

    if D == 10:
        return True
    else:
        self.errorLblPhone["text"] = " * Must be 10-digit phone number without spaces"
        return False

    return True

or even

def validPhone(self):

    # remove previous error message
    self.errorLblPhone["text"] = ""

    D = 0

    for number in self.phone.get():
        if number.isdigit():
            D += 1

    if D != 10:
        self.errorLblPhone["text"] = " * Must be 10-digit phone number without spaces"
        return False

    return True

If number can have only 10 digits (no space, no - , etc):

def validPhone(self):

    # remove previous error message
    self.errorLblPhone["text"] = ""

    D = True

    for number in self.phone.get():
        if not number.isdigit():
            D = False
            break  

    if not D or len(number) != 10:
        self.errorLblPhone["text"] = " * Must be 10-digit phone number without spaces"
        return False

    return True
furas
  • 134,197
  • 12
  • 106
  • 148
  • Thanks furas! The label part works now, at least for resetting if the error message appears. I changed part of the code to return an error message in the form of the label to span next to the `Entry` widget (like in JS and jQuery). `for letter in self.firstName.get(): if not letter.isalpha() and letter not in "'-": return self.errorLblFirst.set(" * Invalid Entry") return True` but I am still getting this error message: `return self.errorLblFirst.set(" * Invalid Entry") AttributeError: 'Label' object has no attribute 'set'` Do you know what I need to change? – annabananana7 Jun 11 '14 at 03:54
  • It seems you didn't read my answer carefully :) There is solution for this problem too. `errorLblFirst["text"] = " * Invalid Entry"`. Label has no `set()` function - it is not `StringVar()`. Read documentation: http://effbot.org/tkinterbook/label.htm , http://effbot.org/tkinterbook/ – furas Jun 11 '14 at 04:50
  • Oh, I see what I did. Since it was in a for loop, I need to use == instead of =. It gave me a syntax error so I changed it back to set(). That seems OK now, but now I have a question about validPhone() [TBC in next comment] – annabananana7 Jun 11 '14 at 21:52
  • I'm using this code: `D = 0 for number in range(len(self.phone)): if self.phone[number].isdigit(): D += 1 if D == 10: return True else: return self.errorLblPhone["text"] == " * Phone number must be 10 digits in length" return` and getting this error: `for number in range(len(self.phone)): TypeError: object of type 'StringVar' has no len()` – annabananana7 Jun 11 '14 at 21:52
  • I add solution in my answer (above). Next time put your code in question (it will be more readable). And add some info here in comment - I got message that there is some comment/info for me. – furas Jun 11 '14 at 22:22
  • I updated to my latest code. Most of the validation works now except for `validBday` and `validEmail` if you could check it out please :) – annabananana7 Jun 12 '14 at 03:38
  • I thought my validPhone() function was working properly since I entered <10 digits, but when I entered exactly 10, the error still shows up. Could you take a look please? All the other validations work - yay! Can't thank you enough for your help so far, furas :) – annabananana7 Jun 12 '14 at 23:45
  • At the beginning I know that there is problem :). You test `if D == 10:` inside `for` loop - change indents to get `if D == 10:` outside that loop. See answer. – furas Jun 13 '14 at 01:33
  • Ohhh I see what you're saying, I've had problems with indentation before. Omg thank you so so much!! I'm finally done :D – annabananana7 Jun 13 '14 at 02:11