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
#1
Hi all. I am making a Tkinter form for registration. I want the register button to be disabled until all fields are filled. secondly I am having an issue with validation command. The validation condition is to allow only 10 digits to be entered in the entry field. I am able to restrict the entry to only digits but unable to restrict the number of digits .

from tkinter import*
import tkinter as tk
import mysql.connector
from tkinter import messagebox

mydb= mysql.connector.connect(host="localhost",user="root", password="mudit",database="clinicmaster",auth_plugin="mysql_native_password")
cursor=mydb.cursor()

def clinic():
    
    clinic_id=ent1.get()
    clinic_name=ent2.get()
    clinic_phone=ent6.get()
    
    sql=("INSERT INTO dentalclinic(clinic_id,clinic_name,clinic_phone)"  "VALUES(%s,%s,%s)" )
    cursor.execute(sql,(clinic_id,clinic_name,clinic_phone))


    if  clinic_id==' 'or  clinic_name==''  or clinic_phone=='':
        messagebox.showinfo("ERROR","fill the empty field!!!")
        button.configure(state=DISABLED)
    else:
        messagebox.showinfo("Confirmation","Clinic Registered")
        button.configure(state=NORMAL)
    mydb.commit()
    messagebox.showinfo("Confirmation","Clinic Added")
    print("DONE")
    return True

    


cursor.execute('select count(*) from dentalclinic')
next_num = cursor.fetchall()[0][0] 

win=Tk()
win.title("ADD Clinic")
win.geometry("600x300")
win.configure(background='light blue')
win.resizable(False,False)


var1=StringVar()
clinic_id=StringVar()
var2=StringVar()
clinic_name=StringVar()
var3=StringVar()
clinic_phone=StringVar()


label= Label(win,text= "Clinic Details",font=('Helvetica 20 italic'),bg="light blue")
label.grid(row=0,column=2,padx=10,pady=10)

label1=Label(win,textvariable=var1,bg="light blue")
var1.set("Clinic ID. ")
label1.grid(row=2,column=1,padx=10,pady=10)

ent1=Entry(win,textvariable=clinic_id,width=10)
clinic_id.set(next_num+1)
ent1.grid(row=2,column=2,sticky=tk.W,padx=10,pady=10)

label2=Label(win,textvariable=var2,bg="light blue")
var2.set("Clinic Name")
label2.grid(row=3,column=1,padx=10,pady=10)

def caps(event):
  clinic_name.set(clinic_name.get().upper())

ent2=Entry(win,textvariable=clinic_name,width=40)
ent2.bind("<KeyRelease>",caps)
clinic_name.set(" ")
ent2.grid(row=3,column=2,padx=10,pady=10)



label3=Label(win,textvariable=var3,bg="light blue")
var3.set("Phone No.")
label3.grid(row=4,column=1,padx=10,pady=10)


def validate_phone(new_value):
   if len(new_value) == 0 or len(new_value) <= 10 and new_value.isdigit():
       return True
   else:
       return False
    
ent3=Entry(win,width=40,textvariable=clinic_phone,validate="key",validatecommand=(win.register(validate_phone), "%S"))
clinic_phone.set(" ")
ent3.grid(row=4,column=2,padx=10,pady=10)


btn=Button(win, text="ADD",command= clinic)
btn.grid(row=5,column=2,padx=10,pady=10,sticky=tk.W)
exit_button = Button(win, text="Exit", command=win.destroy)
exit_button.grid(row=5,column=2,padx=10,pady=10,sticky=tk.E)

win.mainloop()
Reply
#2
In your validator "new_value" is not the value in the entry box, it is the key you pressed. If you want to test for length you need to test the length of clinic_phone.
def validate_phone(key):
    return len(key) == 0 or len(clinic_phone.get()) <= 10 and key.isdigit()
Another way to convert the clinic name to upper case is to add a trace method to the clinic_name variable.
def to_upper(var):
    var.set(var.get().upper())

clinic_name = tk.StringVar(win)
clinic_name.trace('w', lambda a, b, c: to_upper(clinic_name))
A nice thing about this method is I can reuse to_upper() for any number of entries. It is generic.

To enable/disable the add button based on the entry contents, you need to do your test each time a key is pressed. I could trace clinic_id, clinic_name and clinic_phone, but since these are the only entries in the window it is easier to just bind the key release event for the window.
def form_complete(event):
    if  len(clinic_id.get()) < 1 or len(clinic_name.get()) < 1 or len(clinic_phone.get()) < 10:
        add_button.configure(state=tk.DISABLED)
    else:
        add_button.configure(state=tk.NORMAL)

win = tk.Tk()
win.bind("<KeyRelease>", form_complete)
Everything
import tkinter as tk
from tkinter import messagebox
 
def add_clinic():
    id = clinic_id.get()
    name = clinic_name.get()
    phone = clinic_phone.get()

    # Add to database

    clinic_id.set('')
    clinic_name.set('')
    clinic_phone.set('')
    messagebox.showinfo("Confirmation", "Clinic Registered")

def validate_phone(key):
    return len(key) == 0 or len(clinic_phone.get()) <= 10 and key.isdigit()

def to_upper(var):
    var.set(var.get().upper())

def form_complete(event):
    if  len(clinic_id.get()) < 1 or len(clinic_name.get()) < 1 or len(clinic_phone.get()) < 10:
        add_button.configure(state=tk.DISABLED)
    else:
        add_button.configure(state=tk.NORMAL)

win = tk.Tk()
win.title("ADD Clinic")
win.geometry("600x300")
win.configure(background='light blue')
win.resizable(False, False)
win.bind("<KeyRelease>", form_complete)

tk.Label(win, text="Clinic Details", font=('Helvetica 20 italic'), bg="light blue") \
    .grid(row=0, column=2, padx=10, pady=10)
tk.Label(win, text='Clinic ID.', bg="light blue").grid(row=2, column=1, padx=10, pady=10)
tk.Label(win, text='Clinic Name', bg="light blue").grid(row=3, column=1, padx=10, pady=10)
tk.Label(win, text='Phone No.', bg="light blue").grid(row=4, column=1, padx=10, pady=10)

clinic_id = tk.StringVar(win, str(1))
entry = tk.Entry(win, textvariable=clinic_id, width=10)
entry.grid(row=2,column=2,sticky=tk.W,padx=10,pady=10)

clinic_name = tk.StringVar(win)
clinic_name.trace('w', lambda a, b, c: to_upper(clinic_name))
entry = tk.Entry(win, textvariable=clinic_name, width=10)
entry.grid(row=3, column=2, padx=10, pady=10, sticky=tk.W)

clinic_phone = tk.StringVar(win)
entry = tk.Entry(win, width=40, textvariable=clinic_phone, validate="key",\
    validatecommand=(win.register(validate_phone), "%S"))
entry.grid(row=4, column=2, padx=10, pady=10)

add_button = tk.Button(win, text="ADD", command = add_clinic, state=tk.DISABLED)
add_button.grid(row=5, column=2, padx=10, pady=10, sticky=tk.W)

exit_button = tk.Button(win, text="Exit", command=win.destroy)
exit_button.grid(row=5, column=2, padx=10, pady=10, sticky=tk.E)
 
win.mainloop()
Reply
#3
(Oct-18-2021, 10:07 AM)deanhystad Wrote: In your validator "new_value" is not the value in the entry box, it is the key you pressed. If you want to test for length you need to test the length of clinic_phone.
def validate_phone(key):
    return len(key) == 0 or len(clinic_phone.get()) <= 10 and key.isdigit()
Another way to convert the clinic name to upper case is to add a trace method to the clinic_name variable.
def to_upper(var):
    var.set(var.get().upper())

clinic_name = tk.StringVar(win)
clinic_name.trace('w', lambda a, b, c: to_upper(clinic_name))
A nice thing about this method is I can reuse to_upper() for any number of entries. It is generic.

To enable/disable the add button based on the entry contents, you need to do your test each time a key is pressed. I could trace clinic_id, clinic_name and clinic_phone, but since these are the only entries in the window it is easier to just bind the key release event for the window.
def form_complete(event):
    if  len(clinic_id.get()) < 1 or len(clinic_name.get()) < 1 or len(clinic_phone.get()) < 10:
        add_button.configure(state=tk.DISABLED)
    else:
        add_button.configure(state=tk.NORMAL)

win = tk.Tk()
win.bind("<KeyRelease>", form_complete)
Everything
import tkinter as tk
from tkinter import messagebox
 
def add_clinic():
    id = clinic_id.get()
    name = clinic_name.get()
    phone = clinic_phone.get()

    # Add to database

    clinic_id.set('')
    clinic_name.set('')
    clinic_phone.set('')
    messagebox.showinfo("Confirmation", "Clinic Registered")

def validate_phone(key):
    return len(key) == 0 or len(clinic_phone.get()) <= 10 and key.isdigit()

def to_upper(var):
    var.set(var.get().upper())

def form_complete(event):
    if  len(clinic_id.get()) < 1 or len(clinic_name.get()) < 1 or len(clinic_phone.get()) < 10:
        add_button.configure(state=tk.DISABLED)
    else:
        add_button.configure(state=tk.NORMAL)

win = tk.Tk()
win.title("ADD Clinic")
win.geometry("600x300")
win.configure(background='light blue')
win.resizable(False, False)
win.bind("<KeyRelease>", form_complete)

tk.Label(win, text="Clinic Details", font=('Helvetica 20 italic'), bg="light blue") \
    .grid(row=0, column=2, padx=10, pady=10)
tk.Label(win, text='Clinic ID.', bg="light blue").grid(row=2, column=1, padx=10, pady=10)
tk.Label(win, text='Clinic Name', bg="light blue").grid(row=3, column=1, padx=10, pady=10)
tk.Label(win, text='Phone No.', bg="light blue").grid(row=4, column=1, padx=10, pady=10)

clinic_id = tk.StringVar(win, str(1))
entry = tk.Entry(win, textvariable=clinic_id, width=10)
entry.grid(row=2,column=2,sticky=tk.W,padx=10,pady=10)

clinic_name = tk.StringVar(win)
clinic_name.trace('w', lambda a, b, c: to_upper(clinic_name))
entry = tk.Entry(win, textvariable=clinic_name, width=10)
entry.grid(row=3, column=2, padx=10, pady=10, sticky=tk.W)

clinic_phone = tk.StringVar(win)
entry = tk.Entry(win, width=40, textvariable=clinic_phone, validate="key",\
    validatecommand=(win.register(validate_phone), "%S"))
entry.grid(row=4, column=2, padx=10, pady=10)

add_button = tk.Button(win, text="ADD", command = add_clinic, state=tk.DISABLED)
add_button.grid(row=5, column=2, padx=10, pady=10, sticky=tk.W)

exit_button = tk.Button(win, text="Exit", command=win.destroy)
exit_button.grid(row=5, column=2, padx=10, pady=10, sticky=tk.E)
 
win.mainloop()

Thanks deanhystad.
I have a couple of questions;

1) You have used %P after validation command what is the difference is using %P or %S. to me both are placeholders. I have used the length and digit validation function in my order form where age was a digit limited to 2 digits and it worked fine . why is it not working now.
2) Can you explain the use of "str(1)" in line 43"clinic_id = tk.StringVar(win, str(1))
Reply
#4
1) I did not use %P after the validation command, I used %S just like you. I don't know how your validation worked because %S passes the text string being inserted/deleted and there is no way that could be used to stop the entry at 2 characters. %P would actually be a pretty good choice. That would pass what the entry text would be after the edit. These are not placeholders by the way. They affect what is passed to the validator.

https://www.tcl.tk/man/tcl8.4/TkCmd/entry.html#M25

2) I don't have a database, so I don't have a count of clinics. For my purposes I don't care what is in the clinic ID entry, so I put a 1.
Reply
#5
(Oct-20-2021, 04:17 PM)deanhystad Wrote: 1) I did not use %P after the validation command, I used %S just like you. I don't know how your validation worked because %S passes the text string being inserted/deleted and there is no way that could be used to stop the entry at 2 characters. %P would actually be a pretty good choice. That would pass what the entry text would be after the edit. These are not placeholders by the way. They affect what is passed to the validator.

https://www.tcl.tk/man/tcl8.4/TkCmd/entry.html#M25

2) I don't have a database, so I don't have a count of clinics. For my purposes I don't care what is in the clinic ID entry, so I put a 1.

Thanks.
Reply
#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


Possibly Related Threads…
Thread Author Replies Views Last Post
  Tkinter exception if entries are not filled up Reldaing 6 2,386 Jan-09-2020, 05:20 PM
Last Post: Axel_Erfurt
  [PySimpleGui] How to alter mouse click button of a standard submit button? skyerosebud 3 5,012 Jul-21-2019, 06:02 PM
Last Post: FullOfHelp
  [Tkinter] How make a button perform a function after the user inputs numbers Zephyrforce 1 2,437 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