Oct-22-2021, 10:10 PM
(This post was last modified: Oct-22-2021, 10:10 PM by deanhystad.)
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.
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.
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.