Index Error, countdown calendar not working - python

Can you help me with my code?
I wanted an if-else statement, that if there is no description for the festival is given, it displays there is no info about that particular festival. But for that, I would have to do if not(event[2]) but for that, it shows
Traceback (most recent call last):
File "C:\Users\kanav_i4cko4c\Downloads\My first tkinter program.py", line 52, in <module>
if (event[2]):
IndexError: list index out of range
Here is the source code for festivals.txt
Diwali,5/11/21
Christmas,25/12/21
Here is the source code for the main program
from tkinter import Tk, Canvas, simpledialog, messagebox
from datetime import date, datetime
# function get_events is to get the celebration events
def get_events():
list_events = []
with open('festivals.txt') as file:
for line in file:
line1 = line.rstrip('\n')
global current_event
current_event = line1.split(',')
print('Check 0')
print(current_event)
current_event[1] = datetime.strptime(current_event[1], '%d/%m/%y').date()
list_events.append(current_event)
return list_events
def days_between_dates(date1, date2):
time_between = str(date1 - date2)
number_of_days = time_between.split(' ')
return number_of_days[0]
# End of Functions
# -----------------------------------------------------------------------------------------------
# Main program starts here
root = Tk()
root.title('Calendar')
c = Canvas(root, width=2000, height=800, bg='dark blue')
c.pack()
c.create_text(100, 50, anchor='w', fill='white', font=' Times 40 bold underline',
text='My Countdown Calendar')
c.create_rectangle(30, 10, 60, 1000,
outline="#fb0", fill="#fb0")
events = get_events()
today = date.today()
# Make a dictionary for the festivals
vertical_space = 100
events.sort(key=lambda x: x[1])
horizontal_space = 100
for event in events:
event_name = event[0]
days_until = days_between_dates(event[1], today)
if (event[2]):
display = 'It is %s days until %s. %s = %s' % (days_until, event_name, event_name,event[2])
else:
display = 'It is %s days until %s. There is no info on %s' % (days_until, event_name, event_name)
if (int(days_until) <= 50):
text_col = '#c11a2b'
else:
text_col = 'SkyBlue1'
c.create_text(100, vertical_space, anchor='w', fill=text_col,
font='Calibri 28 bold', text=display)
vertical_space = vertical_space + 30
horizontal_space = horizontal_space + 40

When you evaluate event[2] it attempts to retrieve the 3rd item in the list which isn't there so python raises an exception.
One solution could be by initializing your list with default values:
event = [None for i in range(3)]
This way there is a None value at event[2] which can be evaluated by your if statement.
Another solution could be to simply check whether the list contains less than 3 items:
if len(event) <= 2:
# do something

Related

How to put in if statement date format for entry in tkinter?

def Verification():
date_format = "%d/%m/%Y"
if (datetime.strptime("1/1/2001", date_format) <= date_ < datetime.strptime("31/1/2008", date_format)):
print('bravo')
date_= datetime.strptime(date_,date_format)
vt=date_
vt =StringVar()
vt.set('')
lb = Label(parent, text = 'birth day: ')
cp = Entry(parent, textvariable=vt)
bt = Button(parent, text ='Verify', command = Verification)
lb.place(x=30, y=90)
cp.place(x=95, y=90)
bt.place(x=220,y=90)
I'm not sure I understand your question, so the following is an answer based on what I think you're asking.
It works like this: When the Verify button is pressed the verification() function will be called which will initially attempt to parse what the user inputted into a datetime instance. If it cannot do that a error message indicating that will appear. If it succeeds then it next checks to see if it's in the required date range. Again, displaying an error message if it isn't.
from datetime import datetime
from tkinter import *
import tkinter.messagebox as messagebox
DATE_FORMAT = "%d/%m/%Y"
MIN_DATE = datetime.strptime('1/1/2001', DATE_FORMAT)
MAX_DATE = datetime.strptime('31/1/2008', DATE_FORMAT)
def verification():
try:
date_= datetime.strptime(vt.get(), DATE_FORMAT)
except ValueError:
messagebox.showerror('Invalid Date', f'"{vt.get()}"')
return
if MIN_DATE <= date_ < MAX_DATE: # Check date range.
messagebox.showinfo('Bravo', "You entered a valid date!")
else:
messagebox.showerror('Out of range', vt.get())
parent = Tk()
parent.geometry('300x200')
vt = StringVar(value='D/M/Y') # Initially show required format.
lb = Label(parent, text='birth day: ')
cp = Entry(parent, textvariable=vt)
bt = Button(parent, text ='Verify', command=verification)
lb.place(x=30, y=90)
cp.place(x=95, y=90)
bt.place(x=220,y=90)
parent.mainloop()
Screenshot of it running:

Automatically change values of on a canvas tkinter every second

I am building a simple program that gets date and time. I am able to get the values alright, but i am unable to update the values in the canvas. Here is my function that gets the temperature.
from time import strftime
def my_time():
time_string = strftime('%H:%M:%S %p') # time format
return time_string
time = my_time()
canvas.create_text(
1068.0,
744.0,
anchor="nw",
text=time,
fill="#FFFFFF",
font=("Poppins Bold", 120 * -1)
)
window.resizable(False, False)
window.mainloop()
I followed this tutorial, but i am not using the labels they used. I want to use the canvas.create_text. https://www.plus2net.com/python/tkinter-clock.php
How do i make sure the time automatically updates every second? I have tried threading, i have tried .after(1000, functionName) but still. I am new to python so kindly be lenient with the answer.
In my code, i have a separate function for getting the temperature from a sensor, I am able to get the temperature, but i need to update that every second too. But i believe if i am able to do it for the time, i can get it for the temperature too.
Here is the code for getting the temperature.
def read_temp_raw():
with open(device_path +'/w1_slave','r') as f:
valid, temp = f.readlines()
return valid, temp
def read_temp():
global c
valid, temp = read_temp_raw()
while 'YES' not in valid:
time.sleep(0.2)
valid, temp = read_temp_raw()
pos = temp.index('t=')
if pos != -1:
#read the temperature .
temp_string = temp[pos+2:]
temp_c = float(temp_string)/1000.0
temp_c = round(temp_c, 2)
temp_f = temp_c * (9.0 / 5.0) + 32.0
temp_f = round(temp_f, 2)
return temp_c, temp_f
c, f = read_temp()
canvas.create_text(
696.0,
127.0,
anchor="nw",
text=c,
fill="#FFFFFF",
font=("Poppins Bold", 309 * -1)
)
The ID of the text object is returned by create_text. Save it and use it to change the text with canvas.itemconfig:
def update_temp():
c, _ = read_temp()
canvas.itemconfig(temp_text_id, text=str(c))
canvas.after(1000, update_time)
c, f = read_temp()
temp_text_id = canvas.create_text(
1068.0,
744.0,
anchor="nw",
text=str(c),
fill="#FFFFFF",
font=("Poppins Bold", 120 * -1)
)
canvas.after(1000, update_temp)
window.mainloop()

Tkinter python index

I was making a countdown calendar that checks how many days left, name and info, but it is the condition that changes the colour for if more than 50 days part that doesn't work. Can you guys check it?. It checks how many left for each festival. Also as u can see If I change the date to today of any festival, it shows 1 Day left until. I want to fix that
Here is the text from festivals.txt
Dashain,14/10/21,This is Nepal Main Festival
Tihar,6/11/21,Festival of Lights
Christmas,25/12/21,Jingle Bells
New Year,1/01/22,The Day of the new year
from tkinter import Tk, Canvas, simpledialog, messagebox
from datetime import date, datetime
# function get_events is to get the celebration events
def get_events():
list_events = []
with open('festivals.txt') as file:
for line in file:
line1 = line.rstrip('\n')
global current_event
current_event = line1.split(',')
current_event[1] = datetime.strptime(current_event[1], '%d/%m/%y').date()
list_events.append(current_event)
return list_events
# Function to get the dates between the 2
def days_between_dates(date1, date2):
time_between = str(date1 - date2)
number_of_days = time_between.split(' ')
return number_of_days[0]
# End of Functions
# -----------------------------------------------------------------------------------------------
# Main program starts here
root = Tk()
root.title('Calendar')
# Make Canvas
c = Canvas(root, width=2000, height=800, bg='black')
c.pack()
c.create_text(100, 50, anchor='w', fill='cyan', font=' Times 40 bold italic underline',
text='My Countdown Calendar' )
# Store the functions in variables
events = get_events()
today = date.today()
# Aligning the text, sorting the list
vertical_space = 100
events.sort(key=lambda x: x[1])
horizontal_space = 100
# Main Loop
for event in events:
event_name = event[0]
days_until = days_between_dates(event[1], today)
display = 'It is %s days until %s. %s is about %s' % (days_until, event_name, event_name,event[2])
if (int(days_until) > 10):
text_col = '#c11a2b'
remin = 'Come one %s in coming' % (event_name)
c.create_text(550, 500, anchor='w', fill='purple', font='Courier 20 bold underline', text=remin)
else:
text_col = 'lime'
c.create_text(200, vertical_space, anchor='w', fill=text_col,
font='Calibri 28 bold', text=display)
vertical_space = vertical_space + 30
horizontal_space = horizontal_space + 40
There are a couple of problems with the date calculations.
Firstly you can obtain the number of days using the days attribute of the timedelta object returned by date1 - date2 in days_between_dates(). That function could be written as:
def days_between_dates(date1, date2):
return (date1 - date2).days
This is more succinct and less error prone, and now it will return an integer that is more useful in other calculations.
This also fixes a bug. When the dates are the same and str(date1 - date2) returns
'0:00:00' which your code fails to parse.
Regarding the greater than 50 condition, there is no such check in your code. There is a > 10 but your test data does not test that condition. You should add a line with a date within 10 days of the current day.
Finally you will need to handle past events for when the event has already happened. Currently the fixed code would display a negative number of days until the event.

Getting invalid command name in my Tkinter app

When I run my application , its supposed to play an mp3 file when the current time is equal to one of times in a list. The mp3 file is in the same folder as my python file. However, Three things happen. First, the sound doesn't play even though the time is correct and an output is shown in the terminal indicating that the time comparison is true. Second, an extra tkinter window pops up and finally, when I try to close both of the windows, I get these errors:
time to pray at 03:10 PM
invalid command name "2184036301640calc_countdown"
while executing
"2184036301640calc_countdown"
("after" script)
Traceback (most recent call last):
File "d:\My stuff\mypython\Tkinter\azan caller\azan_caller.py", line 266, in <module>
call_azan()
File "d:\My stuff\mypython\Tkinter\azan caller\azan_caller.py", line 152, in call_azan
playsound(azan_path)
File "C:\Users\rahma\AppData\Local\Programs\Python\Python37\lib\site-packages\playsound.py", line 17, in _playsoundWin
from random import random
ImportError: cannot import name 'random' from 'random' (d:\My stuff\mypython\Tkinter\azan caller\random.py)
Random.py is another file in the folder but I never used it in my main python file. This is my full code just for completeness:
import tkinter as tk
from tkinter import Grid
import xlrd
import os
from xlrd import cellname,xldate_as_tuple,xldate_as_datetime
from datetime import date
from datetime import datetime , timedelta
from playsound import playsound
from tkinter import font
#set time and current date
current_time = datetime.strftime(datetime.now(), "%I:%M %p") #time.strftime("%I:%M %p")
todays_date = date.today()
month = todays_date.strftime('%m')
def current_date(todays_date):
format_date = todays_date.strftime("%d/%m/%Y")
return format_date
format_date = current_date(todays_date)
#function to display current time
def display_time():
current_time = datetime.strftime(datetime.now(), "%I:%M %S %p")
lbl_display_time.config(text=current_time)
lbl_display_time.after(1000,display_time)
#function to display current date
def display_date():
todays_date = date.today()
month = todays_date.strftime('%m')
format_date = todays_date.strftime("%d/%m/%Y")
lbl_display_date.config(text=format_date)
lbl_display_date.after(1000,display_date)
#open workbook
filename = os.path.join("D:\My stuff\mypython\Tkinter",'azan caller\prayer_timings.xlsx')
workbook = xlrd.open_workbook(filename)
worksheet = workbook.sheet_by_name('Sheet %s'% month)
def get_date(format_date):
#number of rows in sheet
rows = worksheet.nrows
#get date from excel file and compare with current date
raw_date = ''
for i in range(-1,rows):
raw_date = worksheet.cell_value(i,1)
conv_date = datetime(*xlrd.xldate_as_tuple(raw_date, workbook.datemode))
excel_date = conv_date.strftime("%d/%m/%Y")
if format_date == excel_date:
cell_location = (i,1)
break
return [cell_location , excel_date]
def calc_countdown():
''' This function calculates the remaining time before the next prayer '''
#converts prayer times to datetime object
struc_prayertime_list = [datetime.strptime(i, "%I:%M %p") for i in azan_time]
#gets the current time and converts it to datetime object
current_time = datetime.strptime(datetime.strftime(datetime.now(), "%I:%M:%S %p"), "%I:%M:%S %p")
#gets the next prayer time
nearest_time = next((i for i in struc_prayertime_list if i > current_time), datetime.strptime('01-02 04:18 AM', "%m-%d %I:%M %p"))
#calculates the time left before next prayer, converts it to string and stores it in a variable
time_left_text.set("{} hours {} minutes {} seconds".format(*str(nearest_time - current_time).split(":")))
random_lbl.after(1000, calc_countdown)
#get time for different prayers
def fajr_time(cell_location):
fajr = worksheet.cell_value(cell_location[0][0],3)
return fajr
def zuhr_time(cell_location):
zuhr = worksheet.cell_value(cell_location[0][0],5)
return zuhr
def asr_time(cell_location):
asr = worksheet.cell_value(cell_location[0][0],6)
return asr
def maghrib_time(cell_location):
maghrib = worksheet.cell_value(cell_location[0][0],7)
return maghrib
def isha_time(cell_location):
isha = worksheet.cell_value(cell_location[0][0],8)
return isha
def update_time():
''' this function will run continuoulsy and its function is to keep updating the prayer times
as the day and date changes flow : get the correct cell_location using get_Date() --- set prayer times
using cell_location --- append prayer times(variables) to a list called azan_time--- return azan_time '''
cell_location = get_date(format_date)
#set prayer times
fajr = fajr_time(cell_location)
fajr="0"+fajr
zuhr = zuhr_time(cell_location)
asr = asr_time(cell_location)
maghrib = maghrib_time(cell_location)
isha = isha_time(cell_location)
#store prayer times in a list
azan_time = [fajr,zuhr,asr,maghrib,isha,"3:10 PM"]
#checks if any of the time is in PM. IF so, a '0' is added to the beginning
azan_time.remove(zuhr) #temporarily remove zuhr time
for x in range(0,len(azan_time)):
temp_time = azan_time[x]
if temp_time[-2]+temp_time[-1] == 'PM':
azan_time[x] = '0'+azan_time[x]
azan_time.insert(1,zuhr) # add zuhr time back
random_lbl.after(1000,update_time)
return azan_time #return azan time so it can be used
#function to check if it is time for prayer
def call_azan():
current_time = datetime.strftime(datetime.now(), "%I:%M %p")
for i in range(0,len(azan_time)):
if current_time == azan_time[i]:
print("time to pray at" , azan_time[i])
azan_path = os.path.join("azan caller","azan_sound.mp3")
playsound(azan_path)
else:
continue
random_lbl.after(1000,call_azan)
#GUI
window = tk.Tk()
window.title("Azan Caller")
Grid.rowconfigure(window, 0, weight=1)
Grid.columnconfigure(window, 0, weight=1)
#set up window
height = window.winfo_screenheight()
width = window.winfo_screenwidth()
window.geometry(f'{int(width/2)}x{int(height/2)}')
frm_window = tk.Frame(window,bg="#00BFFF")
frm_window.pack(fill="both",expand=True)
# a label just to run the functions continuously after 1 second
random_lbl = tk.Label(frm_window,width=2)
#create fonts
heading_font = font.Font(family = "ShareTech",size=25,weight='bold')
subheading_font = font.Font(family="ShareTech",size=14)
body_font = font.Font(family="ShareTech",size=10,weight="bold")
# title section
frm_title = tk.Frame(frm_window,relief=tk.SOLID,borderwidth=3,bg="#000000")
frm_title.grid(row=0, column=0, sticky="new",pady=(0,5))
lbl_title = tk.Label(master=frm_title, text="Azan Caller For Abu Dhabi",font=heading_font,fg="#008000")
lbl_title.pack(side='top',fill='x')
#prayer timings table
frm_table = tk.Frame(frm_window,bg="#000000",relief=tk.SOLID,borderwidth=3)
frm_table.grid(row=1,column=0, sticky="nsew",padx=(20,20))
tbl_title = tk.Label(frm_table,text="Today's Prayer Times",relief=tk.SOLID,font=subheading_font,borderwidth=2)
tbl_title.grid(row=0,column=0,columnspan=5,sticky="nsew")
#prayer names
prayer_names = ['Fajr','Zuhr','Asr','Maghrib','Isha']
for j in range(0,len(prayer_names)):
name = prayer_names[j]
cell = tk.Label(frm_table,text=name,relief=tk.SOLID,font=body_font,borderwidth=2)
cell.grid(row=1,column=j,sticky="nsew")
#prayer times
azan_time = update_time()
for i in range(0,len(azan_time)):
prayer_time = azan_time[i]
cell_time = tk.Label(frm_table,text=prayer_time,relief=tk.SOLID,font=body_font,borderwidth=2,fg="#FF8C00")
cell_time.grid(row=2,column=i,sticky="nsew")
#body section
frm_body = tk.Frame(frm_window,relief=tk.SOLID,borderwidth=5,)
frm_body.grid(row=2, column=0, sticky="nsew",pady=(10,10),padx=(20,20))
lbl_date = tk.Label(frm_body,text="Today's Date: ",font=body_font)
lbl_date.grid(row=0,column=0,sticky="nsew",pady=(0,5))
lbl_display_date = tk.Label(frm_body,font=body_font)
lbl_display_date.grid(row=0,column=1,sticky="nsew",pady=(0,5))
lbl_time = tk.Label(frm_body,text="Current Time: ",font=body_font)
lbl_time.grid(row=1,column=0,sticky="nsew",pady=(0,5))
lbl_display_time = tk.Label(frm_body,font=body_font)
lbl_display_time.grid(row=1,column=1,sticky="nsew",pady=(0,5))
time_left_text = tk.StringVar()
lbl_countdown = tk.Label(frm_body,text="Next Prayer in : ",font=body_font)
lbl_countdown.grid(row=2,column=0,sticky="nsew",pady=(0,5))
lbl_display_countdown = tk.Label(frm_body,textvariable=time_left_text,font=body_font,fg="red")
lbl_display_countdown.grid(row=2,column=1,sticky="nsew",pady=(0,5))
#footer
frm_footer = tk.Frame(frm_window,relief=tk.SOLID,borderwidth=3,bg="#000000")
frm_footer.grid(row=3, column=0, sticky="sew",pady=(10,0))
lbl_footer = tk.Label(frm_footer,text="#Copyright AbdulRahman Tijani 2020",font=body_font,fg="#008000")
lbl_footer.pack(side="bottom",fill='x')
#make the structure resizable
#columns
for i in range(frm_window.grid_size()[0]):
frm_window.grid_columnconfigure(i, weight=1)
for i in range(frm_table.grid_size()[0]):
frm_table.grid_columnconfigure(i, weight=1)
for i in range(frm_body.grid_size()[0]):
frm_body.grid_columnconfigure(i,weight=1)
#row
for i in range(frm_window.grid_size()[1]):
frm_window.grid_rowconfigure(i, weight=1)
for i in range(frm_table.grid_size()[1]):
frm_table.grid_rowconfigure(i, weight=1)
for i in range(frm_body.grid_size()[1]):
frm_body.grid_rowconfigure(i,weight=1)
#run functions
display_date()
display_time()
update_time()
call_azan()
calc_countdown()
window.mainloop()
Thank you in Advance
The problem here is because you have a file in your directory called random.py which is being imported here and python is reading that in and not the random module, so try not to name your script files as same as the module names.
And instead of using playsound, why dont you try the pygame module for playing sounds. Refer how to here

How to compare the inputs of two entries using single tkinter function for a bunch of such pairs of entries?

I want to validate two tkinter entries. One called minimum and the other called maximum. Of course, I want to make sure that minimum does not exceed maximum. And there is a third entry called increment which has to be lesser than maximum. There are a set of 15 such entries which I am trying to validate.
I have tried using for loop and tracing the textvariable of each entry. But inside the for loop, I am able to validate only a single entry box. Also, when I skip the validation for that specific one entry called the txtCab, it throws the following exception: If I do it for all the widgets, it does work, but fails some times.
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\beejb\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "C:\PROSAIL_5B_Fortran\PROSAIL_5B_FORTRAN\PROSAIL.py", line 191, in min_max
minVar = eval("self.txtVar_"+ str(wid)+ "_min.get()")
File "<string>", line 1, in <module>
NameError: name 'self' is not defined
The validation function I have used is:
def min_max(*args):
alltextFields = ["N","Cab","Car","Cw","Cm","Cbrown", "rsoil0","LIDFa","LIDFb","TypeLIDF","LAI","hspot","tts","tto","psi" ]
for wid in alltextFields:
if eval("self." + wid + "_variable.get()"):
minVar = eval("self.txtVar_"+ str(wid)+ "_min.get()")
maxVar = eval("self.txtVar_"+ str(wid) + "_max.get()")
rangeVar = eval("self.txtVar_"+ str(wid) + "_range.get()")
##
## print((minVar))
## print((maxVar))
## print((rangeVar))
if len(minVar) > 0 and len(maxVar):
if (minVar) > (maxVar):
messagebox.showinfo("Input Error", "Minimum should not be greater than maximum")
if len(rangeVar) > 0 and len(maxVar) > 0:
if (rangeVar) > (maxVar) :
messagebox.showinfo("Input Error", "Increment cannot exceed maximum limit")
## print(self.txtVar_Cab_min.get()); print(self.txtVar_Cab_max.get());
## print(self.txtVar_N_min.get()); print(self.txtVar_N_max.get());
if len(self.txtVar_Cab_min.get()) > 0 and len(self.txtVar_Cab_max.get()) > 0 and len(self.txtVar_Cab_range.get()) > 0:
if (self.txtVar_Cab_min.get()) > (self.txtVar_Cab_max.get()):
messagebox.showinfo("Input Data Error", "Minimum should not be greater than maximum!!")
if (self.txtVar_Cab_range.get()) > (self.txtVar_Cab_max.get()):
messagebox.showinfo("Error", "Increment cannot exceed maximum!!")
Another validation function I have tried is:
def validateMRM(self,value, text,W):
vMin,vMax,vRange;
entry = self.controller.nametowidget(W)
print(entry)
if entry == self.txt_N_min:
print(entry.get())
print(self.txtVar_N_max.get())
print(self.txtVar_N_range.get())
alltextFields = ["txt_N","txt_Cab","txt_Car","txt_Cab","txt_Cw","txt_Cw","txt_Cm","txt_Cbrown","txt_Cm", "txt_rsoil0",
"txt_LIDFa","txt_LIDFb","txt_TypeLIDF","txt_LAI","txt_hspot","txt_hspot","txt_tts","txt_tto","txt_psi"
]
for wid in alltextFields:
typeOfVar = wid.split("_")
if entry == eval("self.txt_" + str(typeOfVar[1])+ "_min"):
vMin = eval("self.txtVar_" + str(typeOfVar[1])+ "_min.get()")
print(eval("self.txtVar_" + str(typeOfVar[1])+ "_min.get()"))
vMax = eval("self.txtVar_" + str(typeOfVar[1])+ "_max.get()")
print(eval("self.txtVar_" + str(typeOfVar[1])+ "_max.get()"))
vRange = eval("self.txtVar_" + str(typeOfVar[1])+ "_range.get()")
print(eval("self.txtVar_" + str(typeOfVar[1])+ "_range.get()"))
print(vMin); print(vMax); print(vRange)
if len(vMin) > 0 and len(vMax) > 0 and len(vRange) > 0:
if (vMin) > (vMax):
messagebox.showinfo("Error", "Minimum cannot be greater than maximum")
if (vRange) > (vMax) :
messagebox.showinfo("Error", "Increment cannot exceed the maximum limit")
print(len(entry.get()))
if len(entry.get())>2:
And here is how all the entries are created:
self.lbl_N = tk.Label(self,text="Structure Coefficient(N)",anchor="w",width=40,bg='white'); self.lbl_N.grid(row=3,column=4,padx=4,pady=4);
self.N_variable = tk.BooleanVar()
self.chk_N = tk.Checkbutton(self,variable=self.N_variable, command=lambda:self.show_hide()); self.chk_N.grid(row=3,column=6,padx=4,pady=4);
self.txt_N = tk.Entry(self,width=10,validate = 'key', validatecommand = vcmd); self.txt_N.grid(row=3,column=7,padx=4,pady=4);
self.txtVar_N_min = tk.StringVar(); self.txtVar_N_max = tk.StringVar(); self.txtVar_N_range = tk.StringVar();
self.txtVar_N_min.trace("w", min_max); self.txtVar_N_max.trace("w", min_max); self.txtVar_N_range.trace("w", min_max);
self.txt_N_min = tk.Entry(self,width=5,validate = 'key',textvariable=self.txtVar_N_min, validatecommand = vcmd_min_max);
self.txt_N_max = tk.Entry(self,width=5,validate = 'key', textvariable=self.txtVar_N_max,validatecommand = vcmd_min_max);
self.txt_N_range = tk.Entry(self,width=5,validate = 'key', textvariable=self.txtVar_N_range,validatecommand = vcmd_min_max);
There are a set of fourteen such entries and I need to validate each of them.
But none of this gives the actual output I want. It works some time and fails some other times.
I am not sure why is that happening and I have spent a hell of time with this validation.
I'm not sure whether this answers your question but it should point you in the right direction.
I couldn't make much sense of your code. I've produced a 15 row x 4 column grid.
The 4th column is a message that the 3 fields next to it are 'OK' or if not indicate the problem. The validation is run on the whole grid for each keypress. If this is too slow a validate button could launch the validation instead.
import tkinter as tk
from tkinter import ttk
def rec(): return {'lo': 0, 'hi': 0, 'step': 0, 'ok': '' }
root = tk.Tk()
root.title('SO Question')
def entry(id, ent_dict, var_dict, v=0):
""" Add an Entry Widget to the root, with associated StringVar."""
var_dict[id] = tk.StringVar()
var_dict[id].set(str(v))
ent_dict[id] = ttk.Entry(root, textvariable= var_dict[id], width = 10 )
return ent_dict[id]
def do_validate(lo, hi, step):
""" Return OK if lo, hi and step are consistent else an error string. """
if lo < hi and step < hi: return 'OK'
txt = ''
if lo >= hi:
txt = 'lo >= hi. '
if step >= hi:
txt += 'step >= hi.'
return txt
def conv(txt):
""" Convert text to float. Return 0.0 if not valid float e.g "" or 'a' """
try:
return float(txt)
except ValueError:
return 0.0
def oklabel(ent_dict, var_dict):
""" Add an OK Label to a row. """
lo = conv(var_dict['lo'].get())
hi = conv(var_dict['hi'].get())
step = conv(var_dict['step'].get())
var_dict['ok'] = tk.StringVar()
var_dict['ok'].set(do_validate(lo, hi, step))
ent_dict['ok'] = ttk.Label(root, textvariable = var_dict['ok'], width = -17)
return ent_dict['ok'] # Return the Label object for gridding.
def do_check(*args):
""" Loop through the rows setting the validation string in each one. """
for var_dict in stringvars:
lo = conv(var_dict['lo'].get())
hi = conv(var_dict['hi'].get())
step = conv(var_dict['step'].get())
var_dict['ok'].set(do_validate(lo, hi, step))
# Add column labels
ttk.Label(root, text='Minimums').grid(row=0, column=0)
ttk.Label(root, text =' Maximums').grid(row=0, column=1)
ttk.Label(root, text='Increment').grid(row=0, column=2)
ttk.Label(root, text='Valid').grid(row=0, column=3)
# Create containers for he Entries and Stringvars
entries =[]
stringvars = []
# Add 15 rows of Entries / Validation Labels to the UI.
for row in range(1, 16):
tempe=rec()
tempv=rec()
entry('lo', tempe, tempv, 0).grid(row = row, column=0)
entry('hi', tempe, tempv, 0).grid(row = row, column=1)
entry('step', tempe, tempv, 0).grid(row = row, column=2)
oklabel(tempe, tempv).grid(row = row, column = 3)
entries.append(tempe)
stringvars.append(tempv)
# Bind do_check to all Entry widgets.
root.bind_class('TEntry', '<KeyPress>', do_check, add='+')
root.bind_class('TEntry', '<BackSpace>', do_check, add='+')
root.bind_class('TEntry', '<Delete>', do_check, add='+')
root.mainloop()
In the past I've got stuck trying to validate multiple fields by not allowing inconsistent entries. It is difficult for users to follow what is required to correct fields. They have to work in the correct order. e.g. lo = 100, hi = 9, and step = 1. Should the UI allow the last zero in 100 to be deleted, leaving 10 which is gt 9?
This could be extended to activate a 'Next' button only if all rows are OK.
Edit 1 - Response to Comment
This has a function to create and activate each row of the display. Each row has it's own variables and checking function. They are triggered by the trace on the three Entry StringVars, there's no need to use validate.
import tkinter as tk
from tkinter import ttk
def to_float(txt):
""" Safely convert any string to a float. Invalid strings return 0.0 """
try:
return float(txt)
except ValueError:
return 0.0
def row_n( parent, n, init_show = 0 ):
""" Create one row of the display. """
# tk.Variables
v_show = tk.IntVar()
v_min = tk.StringVar()
v_max = tk.StringVar()
v_incr = tk.StringVar()
v_message = tk.StringVar()
# Initialise variables
v_min.set('0')
v_max.set('1')
v_incr.set('1') # Can the increment be zero?
v_show.set(init_show)
v_message.set("OK")
def do_trace(*args):
""" Runs every time any of the three Entries change value.
Sets the message to the appropriate text.
"""
lo = to_float(v_min.get())
hi = to_float(v_max.get())
inc = to_float(v_incr.get())
if lo < hi and inc <=hi:
v_message.set('OK')
else:
txt = ''
if lo >= hi:
txt += 'Min >= Max'
if inc > hi:
if len(txt): txt += ' & '
txt += 'Incr > Max'
v_message.set(txt)
# Set trace callback for changes to the three StringVars
v_min.trace('w', do_trace)
v_max.trace('w', do_trace)
v_incr.trace('w', do_trace)
def activation(*args):
""" Runs when the tickbox changes state """
if v_show.get():
e_min.grid(row = n, column = 1)
e_max.grid(row = n, column = 2)
e_inc.grid(row = n, column = 3)
message.grid(row = n, column = 4)
else:
e_min.grid_remove()
e_max.grid_remove()
e_inc.grid_remove()
message.grid_remove()
tk.Checkbutton(parent,
text = 'Structure Coefficient {} :'.format(n),
variable = v_show, command = activation ).grid(row = n, column = 0)
e_min = tk.Entry(parent, width=5, textvariable = v_min)
e_max =tk.Entry(parent, width=5, textvariable = v_max)
e_inc = tk.Entry(parent, width=5, textvariable = v_incr)
message = tk.Label(parent, width=-15, textvariable = v_message)
activation()
return { 'Min': v_min, 'Max': v_max, 'Inc': v_incr }
def show_results():
print('Min Max Inc')
for row in rows:
res = '{} {} {}'.format(row['Min'].get(), row['Max'].get(), row['Inc'].get())
print( res )
root = tk.Tk()
root.title('SO Question')
ttk.Label(root, text='Minimums').grid(row=0, column=1)
ttk.Label(root, text =' Maximums').grid(row=0, column=2)
ttk.Label(root, text='Step', width = 5 ).grid(row=0, column=3)
ttk.Label(root, text='Valid', width = 15 ).grid(row=0, column=4)
rows = []
for r in range(1,16):
rows.append(row_n(root, r, init_show=r%3 == 0 ))
tk.Button(root, command=show_results, text = ' Show Results ').grid(column=1, pady = 5)
root.mainloop()
This is another approach. Does this help.
Here's another suggestion. Incorporate the Label and Entry in the row-n function. Include activating / disabling the Entry in the activate function. The row_n function is executed in a loop through a list of the descriptions you want.
import tkinter as tk
row_names = [ "Structure Coefficient(N)", "Chlorophyll Content(Cab) (µg.cm-2)",
"Carotenoid content(Car) (µg.cm-2)", "Brown pigment content(Cbrown)(arbitrary units)"]
def row_n(parent, desc, n, init_show = 0 ):
""" Create one row of the display. """
# tk.Variables
v_show = tk.IntVar()
v_min = tk.StringVar()
v_max = tk.StringVar()
v_incr = tk.StringVar()
v_fixed = tk.StringVar() # New StringVar
v_message = tk.StringVar()
v_show.set(init_show)
v_message.set("OK")
def do_trace(*args):
""" Runs every time any of the three Entries change value.
Sets the message to the appropriate text.
"""
lo = to_float(v_min.get())
hi = to_float(v_max.get())
inc = to_float(v_incr.get())
if lo < hi and inc <=hi:
v_message.set('OK')
else:
txt = ''
if lo >= hi:
txt += 'Min >= Max'
if inc > hi:
if len(txt): txt += ' & '
txt += 'Incr > Max'
v_message.set(txt)
# Set trace callback for changes to the three StringVars
v_min.trace('w', do_trace)
v_max.trace('w', do_trace)
v_incr.trace('w', do_trace)
def activation(*args):
""" Runs when the tickbox changes state """
if v_show.get():
e_min.grid(row = n, column = 8)
e_max.grid(row = n, column = 9)
e_inc.grid(row = n, column = 10)
message.grid(row = n, column = 11)
e_fixed.config(state = 'disabled') # Disable the base Entry
else:
e_min.grid_remove()
e_max.grid_remove()
e_inc.grid_remove()
message.grid_remove()
e_fixed.config(state = 'normal') # Enable the base Entry Widget
tk.Label(parent, text = desc ).grid(row = r+1, column = 4 ) # Add the desc. Label
e_fixed = tk.Entry(parent, textvariable = v_fixed) # Add the new Entry widget
e_fixed.grid(row = r+1, column = 5)
tk.Checkbutton(parent,
text = ' '.format(n),
variable = v_show, command = activation ).grid(row = n, column = 6)
e_min = tk.Entry(parent, width=5, textvariable = v_min)
e_min.config(font=('Candara', 15))
e_max =tk.Entry(parent, width=5, textvariable = v_max)
e_max.config(font=('Candara', 15))
e_inc = tk.Entry(parent, width=5, textvariable = v_incr)
e_inc.config(font=('Candara', 15))
message = tk.Label(parent, width=-15, textvariable = v_message)
message.config(font=('Candara', 15))
activation()
return { 'Min': v_min, 'Max': v_max, 'Inc': v_incr, 'Fixed': v_fixed }
# The 'Fixed' field added to the dictionary to return
def print_row(row):
fmt = 'Min: {}, Max: {}, Inc: {}, Fixed: {}'
print(fmt.format(
row['Min'].get(), row['Max'].get(), row['Inc'].get(), row['Fixed'].get()
))
def to_float(txt):
""" Safely convert any string to a float. Invalid strings return 0.0 """
try:
return float(txt)
except ValueError:
return 0.0
# GUI Start
root = tk.Tk()
root.title('Validation wth Trace')
# Header Labels
tk.Label(root,text="Min").grid(row=0,column=8,padx=4,pady=4)
tk.Label(root,text="Max").grid(row=0,column=9,padx=4,pady=4)
tk.Label(root,text="Inc").grid(row=0,column=10,padx=4,pady=4)
# Body of rows
rows = []
for r, r_text in enumerate(row_names):
rows.append(row_n( root, r_text, r+1))
root.mainloop()
print("Strings in the Entry fields")
for r, row in enumerate(rows):
print('Row: ', r, 'Data:', end=' ')
print_row(row)
HTH. Seeing your code in the inked question you may prefer to make row_n a class.

Categories