Python Forum
button enabled only when all the inputs are filled
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
button enabled only when all the inputs are filled
#6
There is a lot of duplication in the code. I hate duplication. When I see duplicate code I cannot help but look for ways to remove it.

One of my favorite ways to remove duplicate code is to treat things generically. This program implements a data entry form. Forms have fields. A field is an Entry for displaying/editing the field value, and a label to identify the field. The entry may have some special validation code.

In another post I implemented form fields using a class. Some people just don't think they are ready for classes (everyone is ready for classes), so here is a solution that uses dictionaries.
import tkinter as tk
from tkinter import messagebox

# This is a dictionary mapping names that will appear on the form
# to their database field names.  This dictionary can be used to make
# database queries as well as populate the form
field_names = {
  'Clinic ID.': 'clinic_id',
  'Name': 'clinic_name',
  'Address': 'clinic_address',
  'City': 'clinic_city',
  'State': 'clinic_state',
  'Phone': 'clinic_phone',
  'Email': 'clinic_email',
  'Website': 'clinic_website'
}

# These will eventually be dictionaries of StringVars and Entries.
fields = {}
entries = {}

# These are some functions that the fields might use.  Notice that I put them together near the top of the file.
def to_upper(field):
    '''Convert all chars to upper case'''
    field.set(field.get().upper())

def validate_phone(value):
    '''value is the Entry text WITH the edit applied.  Use %P when configuring the validator.'''
    # Any characters must be digits.  Cannot have more than 10 digits.
    return len(value) == 0 or len(value) <= 10 and value.isdigit()

def form_complete(event):
    '''Is there something in every field?  Is the phone entry filled?'''
    if any([len(field.get()) == 0 for field in fields.values()]) or len(fields['Phone'].get()) < 10:
        add_button.configure(state=tk.DISABLED)
    else:
        add_button.configure(state=tk.NORMAL)

def add_clinic():
    '''Add clinic to database.  For now just print the fields.'''
    values = [entry.get() for entry in entries.values()]

    # Print out the form info in a nice tabular format
    for key in field_names:
        print(f'{key:10} {field_names[key]:20} {fields[key].get()}')

    # Show how the field_names dictionary can be used to make a database query
    sql = "INSERT INTO dentalclinic(" + \
        ', '.join(field_names.values()) + \
        ') VALUES(' + \
        ', '.join(['%s'] * len(field_names)) + \
        ')'
    print(sql)  # Can use these to set values in a database
    print(values)

    messagebox.showinfo("Confirmation", "Clinic Registered")


# This is the code that makes the form
win=tk.Tk()
win.bind("<KeyRelease>", form_complete)  # This will enable the register button when the field is completed

# Make labels and entries for all of the fields.  For now they are all the same
for i, name in enumerate(field_names):
    tk.Label(win, text=name).grid(row=i, column=0, sticky='E')
    field = tk.StringVar(win, '')
    entry = tk.Entry(win, textvariable=field, width=40)
    entry.grid(row=i, column=1, padx=10, pady=5, sticky='W')
    fields[name] = field
    entries[name] = entry

# You can customize some of the fields here.  Use keys from the field_names dictionary to get StringVars or Entries
# These fields are shorter
for key in ('Clinic ID.', 'Phone'):
    entries[key].configure(width=10)

# These fields are upper case
for key in ('Name', 'Address', 'City', 'State'):
    field = fields[key]
    field.trace('w', lambda a, b, c: to_upper(field))

# Limit the phone number to 10 characters.  The phone validator wants the phone number with the edit (use %P)
entries['Phone'].configure(validate="key", validatecommand=(win.register(validate_phone), "%P"))

# Create buttons in their own frame to improve the form layout,
buttons = tk.Frame(win)
buttons.grid(row=i+1, column=0, columnspan=2, sticky='EW')
add_button = tk.Button(buttons, text="REGISTER", command=add_clinic, state=tk.DISABLED, width=15)
add_button.pack(side=tk.LEFT, pady=10, expand=True)
exit_button = tk.Button(buttons, text="Exit", command=win.destroy, width=15)
exit_button.pack(side=tk.LEFT, expand=True)

win.mainloop()
This only reduces the lines of codes by about 10 to 15 lines, but it I think it results in code that is easier to read, easier to maintain, and resistant to errors. For example, right now I do not set colors or fonts. Previously, if I wanted to change the form color I had to edit modify each label and button to set the new color. Now since I have a loop I only have to change one line of code and all the labels are drawn in the correct color.
win.configure(bg='light blue')  # set the color here
...
    tk.Label(win, text=name, bg=win['bg']).grid(row=i, column=0, sticky='E')  # adopt color here
...
buttons = tk.Frame(win, bg=win['bg'])  # And here
...
add_button = tk.Button(buttons, text="REGISTER", command=add_clinic, state=tk.DISABLED, width=15, bg=buttons['bg']) # and here
exit_button = tk.Button(buttons, text="Exit", command=win.destroy, width=15, bg=buttons['bg'])
After making these changes to the code it is now really easy to change the background color to coral (Please don't do this!).
win.configure(bg='coral')
Now the labels and buttons automatically adopt the color of the window so only one line of code is changed.

Earlier I gave bad advice in a PM about the phone validator. I said that len(value) == 0 was redundant because 0 <= 10. Running the code this is obviously incorrect. To allow deleting all the characters the validator needs to ignore value.isdigit() when len(value) == 0.
Reply


Messages In This Thread
RE: button enabled only when all the inputs are filled - by deanhystad - Oct-22-2021, 10:10 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Tkinter exception if entries are not filled up Reldaing 6 3,691 Jan-09-2020, 05:20 PM
Last Post: Axel_Erfurt
  [PySimpleGui] How to alter mouse click button of a standard submit button? skyerosebud 3 6,093 Jul-21-2019, 06:02 PM
Last Post: FullOfHelp
  [Tkinter] How make a button perform a function after the user inputs numbers Zephyrforce 1 3,138 May-22-2019, 05:43 PM
Last Post: woooee

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020