Related
I have a countdown program (code below) that should start counting down from whatever length of time you have put in (you all know how a timer works, right?). But the tkinter window simply freezes when you press start but does not return any error until a windows error message saying that the window is not responding pops up. I have included some print statements as I was trying to debug it and it shows in shell that the program is working as it repeatedly prints c .Does anyone know how to sort out this problem?
import tkinter as tk
import time
def change(direction, stringvar, up_button, down_button):
if direction == 'up':
stringvar.set(stringvar.get()+1)
down_button.config(state = 'normal')
if int(stringvar.get())== 59:
up_button.config(state = 'disabled')
if direction == 'down':
stringvar.set(stringvar.get()-1)
up_button.config(state = 'normal')
if stringvar.get()== 0:
down_button.config(state = 'disabled')
def startTimer():
hourEntry.destroy()
minuteEntry.destroy()
secondEntry.destroy()
print('a')
hourLab = tk.Label(root, textvariable = hourText)
minuteLab = tk.Label(root, textvariable = minuteText)
secondLab = tk.Label(root, textvariable = secondText)
print('b')
hourLab.grid(row = 1, column = 0)
minuteLab.grid(row = 1, column = 1)
secondLab.grid(row = 1, column = 2)
while hourText.get() != 0 or minuteText != 0 or secondText !=0:
print('c')
time.sleep(1)
secondText.set(secondText.get()-1)
if int(secondText.get()) == 0:
secondText.set(59)
if int(minuteText.get()) == 0:
if int(hourText.get()) == 0:
continue
else:
hourText.set(str(int(hourText.get())-1))
else:
minuteText.set(str(int(minuteText.get())-1))
root = tk.Tk()
root.title('Timer')
hourText = tk.IntVar()
minuteText = tk.IntVar()
secondText = tk.IntVar()
hourText.set(1)
minuteText.set(1)
secondText.set(1)
## create buttons and entry boxes using loop
up1 = tk.Button(root, text = '^^^', command = lambda: change('up', hourText, up1, down1))
up2 = tk.Button(root, text = '^^^', command = lambda: change('up', minuteText, up2, down2))
up3 = tk.Button(root, text = '^^^', command = lambda: change('up', secondText, up3, down3))
hourEntry = tk.Entry(root, textvariable = hourText, width = 5)
minuteEntry = tk.Entry(root, textvariable = minuteText, width = 5)
secondEntry = tk.Entry(root, textvariable = secondText, width = 5)
down1 = tk.Button(root, text = '...', command = lambda: change('down', hourText, up1, down1))
down2 = tk.Button(root, text = '...', command = lambda: change('down', minuteText, up2, down2))
down3 = tk.Button(root, text = '...', command = lambda: change('down', secondText, up3, down3))
start = tk.Button(root, text = 'Start', command = startTimer)
up1.grid(row = 0, column = 0, pady = 5, padx = 5)
up2.grid(row = 0, column = 1)
up3.grid(row = 0, column = 2, padx = 5)
hourEntry.grid(row = 1, column = 0, padx = 2, pady = 5)
minuteEntry.grid(row = 1, column = 1, padx = 2, pady = 5)
secondEntry.grid(row = 1, column = 2, padx = 2, pady = 5)
down1.grid(row = 2, column = 0)
down2.grid(row = 2, column = 1)
down3.grid(row = 2, column = 2)
start.grid(row = 3, columnspan = 3, pady = 5)
root.mainloop()
To implement your program :
import tkinter as tk
import time
def change(direction, stringvar, up_button, down_button):
if direction == 'up':
stringvar.set(stringvar.get()+1)
down_button.config(state = 'normal')
if int(stringvar.get())== 59:
up_button.config(state = 'disabled')
if direction == 'down':
stringvar.set(stringvar.get()-1)
up_button.config(state = 'normal')
if stringvar.get()== 0:
down_button.config(state = 'disabled')
def timer():
if hourText.get() != 0 or minuteText != 0 or secondText !=0:
print('c')
time.sleep(1)
secondText.set(secondText.get()-1)
if int(secondText.get()) == 0:
secondText.set(59)
if int(minuteText.get()) == 0:
if int(hourText.get()) == 0:
pass
else:
hourText.set(str(int(hourText.get())-1))
else:
minuteText.set(str(int(minuteText.get())-1))
root.after(1, timer)
def startTimer():
hourEntry.destroy()
minuteEntry.destroy()
secondEntry.destroy()
print('a')
hourLab = tk.Label(root, textvariable = hourText)
minuteLab = tk.Label(root, textvariable = minuteText)
secondLab = tk.Label(root, textvariable = secondText)
print('b')
hourLab.grid(row = 1, column = 0)
minuteLab.grid(row = 1, column = 1)
secondLab.grid(row = 1, column = 2)
root.after(1, timer)
root = tk.Tk()
root.title('Timer')
hourText = tk.IntVar()
minuteText = tk.IntVar()
secondText = tk.IntVar()
hourText.set(1)
minuteText.set(1)
secondText.set(1)
## create buttons and entry boxes using loop
up1 = tk.Button(root, text = '^^^', command = lambda: change('up', hourText, up1, down1))
up2 = tk.Button(root, text = '^^^', command = lambda: change('up', minuteText, up2, down2))
up3 = tk.Button(root, text = '^^^', command = lambda: change('up', secondText, up3, down3))
hourEntry = tk.Entry(root, textvariable = hourText, width = 5)
minuteEntry = tk.Entry(root, textvariable = minuteText, width = 5)
secondEntry = tk.Entry(root, textvariable = secondText, width = 5)
down1 = tk.Button(root, text = '...', command = lambda: change('down', hourText, up1, down1))
down2 = tk.Button(root, text = '...', command = lambda: change('down', minuteText, up2, down2))
down3 = tk.Button(root, text = '...', command = lambda: change('down', secondText, up3, down3))
start = tk.Button(root, text = 'Start', command = startTimer)
up1.grid(row = 0, column = 0, pady = 5, padx = 5)
up2.grid(row = 0, column = 1)
up3.grid(row = 0, column = 2, padx = 5)
hourEntry.grid(row = 1, column = 0, padx = 2, pady = 5)
minuteEntry.grid(row = 1, column = 1, padx = 2, pady = 5)
secondEntry.grid(row = 1, column = 2, padx = 2, pady = 5)
down1.grid(row = 2, column = 0)
down2.grid(row = 2, column = 1)
down3.grid(row = 2, column = 2)
start.grid(row = 3, columnspan = 3, pady = 5)
root.mainloop()
Tkinter uses a method (Tk.after) that allow the user to overcome the tk mainloop. (The window does not wait for the function to finish).
It allows us to update manually all the widgets (canvas, button, label [ect...])
This is a method I always use in my tkinter applications, because I don't trust tk.mainloop () to do what I want.
It is beacause you used a while loop. Your window is waiting for it to end.
You should use intstead a window.after(time, target) :
import tkinter
window = tk.Tk()
def task():
# do things here
window.after(1, task)
window.after(100, task)
window.mainloop()
I want to pass a button name to tkinter as a variable. This method works for Text fields, but not for buttons in my code.
I am building a gui app and I want to have a generic clear function that zaps a text entry field and resets a button from NORMAL to DiSABLED.
There are multiple buttons and fields, hence the desire to make this generic.
For the code I have, the buttons are present with the exception of clear all.
I am setting the variable w_button to the specific name of the (existing) button based on what is passed to the function.
def switch_clear(elem_type):
if elem_type == 'scn':
w_button = 'b_clear_scn'
clear_field = 'scn_file_entry'
print ('scenario')
elif elem_type == 'sol2':
w_button = 'b_clear_sol2'
clear_field = 'sol2_file_entry'
print ('sol2')
elif elem_type == 'mdl':
w_button = 'b_clear_mdlList'
clear_field = 'mdlList_file_entry'
print ('mdl')
elif elem_type == 'all':
print ('clear all TBD')
return()
if w_button["state"] == NORMAL:
clear_field.delete(0, END)
w_button["state"] = DISABLED
return()
Here's what happens:
C:\utils>my_frame3.py
scenario
Traceback (most recent call last):
File "C:\utils\my_frame3.py", line 127, in <module>
b_clear_scn = Button(first_frame, text = "Clear scenario", command = switch_clear('scn'), height = 2, state=DISABLED)
File "C:\utils\my_frame3.py", line 100, in switch_clear
if w_button["state"] == NORMAL:
TypeError: string indices must be integers
C:\utils>
I realize I could duplicate and push the clear operations into the if/elif statements, and I may have to live with that but - is it possible to reference buttons as variables? How can I do this?
Complete code as requested below. The preview is a mess despite using the code widget, but in my file lines are correctly formatted (notepad++). Tx
import os, sys
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
root2 = Tk()
root2.title('Model Processing')
root2.geometry('{}x{}'.format(512, 400))
# colors
color1 = 'light cyan'
color2 = 'gold'
color3 = 'RosyBrown1'
color4 = 'lavender'
color5 = 'linen'
bg_color = 'azure'
# number of items to process; until >1 OK is disabled; if >1 OK is enabled TBD
l_to_do = [] # items to process; scn, sol2, modelList, installed models
# functions/commands
def callback():
print ('A button was clicked.')
return;
def my_exit():
result = messagebox.askquestion("Cancel Model Processing", "Are you sure?", icon='warning')
if result == 'yes':
root2.quit()
else: # user changed mind
pass
def choose_import_file(str_ftype):
which_field = ''
which_clear_btn = ''
ftype = str_ftype
mdl_opts = {}
if ftype == 'scn':
title_msg = 'Choose a scenario file'
mdl_opts['filetypes'] = [('Supported types', ('.scn')),
('scenario files',('.scn'))]
which_field = scn_file_entry
which_clear_btn = 'b_clear_scn'
elif ftype == 'sol2':
title_msg = 'Choose a SOL2 file'
mdl_opts['filetypes'] = [('Supported types', ('.txt')),
('sol2 files',('.txt'))]
which_field = sol2_file_entry
elif ftype == 'mdllist':
title_msg = 'Choose a ModelList file'
print ('TBD: ModelList file')
file_input = filedialog.askopenfilename(title = title_msg, **mdl_opts)
if file_input == '':
print ('error or cancelled by user')
return
else:
f_inp_file = os.path.basename(file_input)
f_inp_file_base = str(file_input.split('.')[0])
f_inp_file_ext = str.lower(str(file_input.split('.')[1]))
f_inp_d_name = os.path.dirname(file_input)
print('File chosen:', f_inp_file_base)
# populate scenario file field
which_field.insert(INSERT,file_input)
which_clear_btn["state"] = NORMAL
# define appropriate clear button active
# define_clear_btn.configure(state = ACTIVE)
return
def switch_clear(elem_type):
if elem_type == 'scn':
if b_clear_scn["state"] == NORMAL:
scn_file_entry.delete(0, END)
b_clear_scn["state"] = DISABLED
elif elem_type == 'sol2':
f b_clear_sol2["state"] == NORMAL:
clear_field = 'sol2_file_entry'
b_clear_sol2["state"] = DISABLED
elif elem_type == 'mdl':
if b_clear_mdlList["state"] == NORMAL:
clear_field = 'mdlList_file_entry'
b_clear_mdlList["state"] = DISABLED
elif elem_type == 'all':
print ('clear all TBD')
return()
return()
# create all of the main containers
first_frame = Frame(root2, bg=color5, width = 512, height=90, pady=10)
second_frame = Frame(root2, bg=color5, width = 512, height=90, pady=10)
third_frame = Frame(root2, bg=color5, width=512, height=90, pady=10)
fourth_frame = Frame(root2, bg=color5, width = 512, height = 90, pady=10)
# layout all of the main containers
root2.grid_rowconfigure(3, weight=1)
root2.grid_rowconfigure(2, weight=1)
root2.grid_rowconfigure(1, weight=1)
root2.grid_columnconfigure(0, weight=1)
first_frame.grid(row=0, sticky="ew")
second_frame.grid(row=1, sticky="ew")
third_frame.grid(row=2, sticky="ew")
fourth_frame.grid(row = 3, sticky="e")
# create the widgets for the first frame
#scn_label = Label(first_frame, text = 'Scenario file')
scn_file_entry = Entry(first_frame, background=bg_color, width = 50)
b_choose_scn = Button(first_frame, text = "Choose a scenario..", command = lambda: choose_import_file('scn'), height = 2)
b_clear_scn = Button(first_frame, text = "Clear scenario", command = switch_clear('scn'), height = 2, state=DISABLED)
# layout the widgets in the first frame
#scn_label.grid(row = 0, column = 0, padx = (10,50), pady=5)
scn_file_entry.grid(row = 0, column = 1, padx = (10,10))
b_choose_scn.grid(row=0, column=0, padx = (10,10), sticky=W)
b_clear_scn.grid(row=2, column=0, padx = (10,10), sticky=W)
# second frame
# sol2_label = Label(second_frame, text = 'Sol2 file')
sol2_file_entry = Entry(second_frame, background=bg_color, width = 50)
b_choose_sol2 = Button(second_frame, text = "Choose SOL2 file..", command = lambda: choose_import_file('sol2'), height = 2)
b_clear_sol2 = Button(second_frame, text = "Clear SOL2", command = switch_clear('sol2'), height = 2, state=DISABLED)
# layout the widgets in the second frame
# sol2_label.grid(row = 0, column = 0, padx = (10,50), pady=5)
sol2_file_entry.grid(row = 0, column = 1, padx = (10,10), sticky=EW)
b_choose_sol2.grid(row=0, column=0, padx = (10,10), sticky=W)
b_clear_sol2.grid(row=2, column=0, padx = (10,10), sticky=W)
# third frame
# mdlList_label = Label(third_frame, text = 'ModelList.txt file')
mdlList_file_entry = Entry(third_frame, background=bg_color, width = 50)
b_choose_mdlList = Button(third_frame, text = "Choose ModelList.txt file..", command = callback, height = 2)
b_clear_mdlList = Button(third_frame, text = "Clear ModelList", command = callback, height = 2, state=DISABLED)
# layout the widgets in the third frame
#mdlList_label.grid(row = 0, column = 0, padx = (10,10), pady=5, sticky = 'ns')
mdlList_file_entry.grid(row = 0, column = 1, padx = (10,10), sticky=EW)
b_choose_mdlList.grid(row=0, column=0, padx = (10,10), sticky=W)
b_clear_mdlList.grid(row=2, column=0, padx = (10,10), sticky=W)
#####################################################################
# create bottom widgets
#####################################################################
clear_all = Button(fourth_frame, text='Clear All', padx = '5', command = callback, height = 2, state=DISABLED)
ok_btn = Button(fourth_frame, text='OK', padx = '5', command = callback, height = 2, state=DISABLED)
cancel_btn = Button(fourth_frame, text='Cancel', height = 2, padx = '12', command = my_exit)
#####################################################################
# layout the bottom widgets
#####################################################################
clear_all.grid(row = 0, column = 2, sticky = 'e')
ok_btn.grid(row = 0, column = 3, sticky = 'e')
cancel_btn.grid(row = 0, column = 4, sticky = 'e')
# commands/bindings
root2.mainloop()
I believe this is what you are trying to do. The below is fully commented with explanations. switch_clear becomes entirely unnecessary with this method. The buttons directly clear their corresponding entry in their command, and Entry validation takes care of the corresponding button state.
Note: I wrote all of this before you posted your full code
import tkinter as tk
root = tk.Tk()
root.geometry('400x400')
#toggle the state of buttons based on entry text length
def toggle_button(name, text):
global btn_switch
btn_switch[name]['state'] = 'normal' if len(text) else 'disabled'
return True
#to hold the buttons so they are easy to position
#just for this example
buttons = tk.Frame(root)
buttons.pack(side='top', anchor='nw')
#create a tcl wrapper for the validate command
vcmd = tk.Widget.register(root, toggle_button)
#mock-up of your entries ~ validate on key press. send widget name and full text to vcmd
scn_file_entry = tk.Entry(root, width=20)
scn_file_entry.configure(validate="key", validatecommand=(vcmd, '%W', '%P'))
scn_file_entry.pack(side='left', anchor='nw')
sol2_file_entry = tk.Entry(root, width=20)
sol2_file_entry.configure(validate="key", validatecommand=(vcmd, '%W', '%P'))
sol2_file_entry.pack(side='left', anchor='nw')
mdlList_file_entry = tk.Entry(root, width=20)
mdlList_file_entry.configure(validate="key", validatecommand=(vcmd, '%W', '%P'))
mdlList_file_entry.pack(side='left', anchor='nw')
#mock-up of your buttons ~ delete the entry text in a lambda and let entry validation handle the button state
b_clear_scn = tk.Button(buttons, text="scn", state='disabled')
b_clear_scn.configure(command=lambda: scn_file_entry.delete(0, 'end'))
b_clear_scn.pack(side='left', anchor='nw')
b_clear_sol2 = tk.Button(buttons, text="sol2", state='disabled')
b_clear_sol2.configure(command=lambda: sol2_file_entry.delete(0, 'end'))
b_clear_sol2.pack(side='left', anchor='nw')
b_clear_mdlList = tk.Button(buttons, text="mdl", state='disabled')
b_clear_mdlList.configure(command=lambda: mdlList_file_entry.delete(0, 'end'))
b_clear_mdlList.pack(side='left', anchor='nw')
#create a dictionary of 'widget name':corresponding button, for toggle_button to reference
btn_switch = {
f'{scn_file_entry}':b_clear_scn,
f'{sol2_file_entry}':b_clear_sol2,
f'{mdlList_file_entry}':b_clear_mdlList,
}
root.mainloop()
I have written a code for a basic game but the image and shape don't show up unless I add something like item.pack() or win.mainloop() [which doesn't really make sense] but then the lines below it don't run.
When I don't have anything, the buttons show up but the image doesn't show up.
import tkinter as tk
import random
from tkinter import messagebox
win = tk.Tk()
my_label = tk.Label(win, text="Color of the Baloon Game")
my_label.pack()
my_canvas = tk.Canvas(win, width=400, height=600)
my_canvas.pack()
background_image=tk.PhotoImage(file = "CS_Game_menu.png")
background_label = tk.Label(my_canvas, image=background_image)
background_label.photo = background_image
background_label.grid(row = 0, rowspan = 10, column = 0, columnspan = 10)
def drawCircle():
color = "green"
x1 = 265
y1 = 80
diameter = 90
my_canvas.destroy()
circle_button.destroy()
quit_button.destroy()
my_label.destroy()
my_label1 = tk.Label(win, text="What is the Color of the Baloon?", font="Purisa")
my_label1.pack()
my_canvas1 = tk.Canvas(win, width=400, height=600)
my_canvas1.pack()
image1 = r"CS_Game_baloon.png"
photo1 = tk.PhotoImage(file=image1)
item = my_canvas1.create_image(200, 350, image=photo1)
shape = my_canvas1.create_oval(x1, y1, x1 + diameter, y1 + diameter+20, fill=color)
item.pack()
game1_button = tk.Button(my_canvas1, text = "Green")
game1_button.grid(row= 8, column = 3)
game1_button["command"] = lambda: messagebox.showinfo("Congratulations!", "Correct Answer!")
game2_button = tk.Button(my_canvas1, text = "Blue")
game2_button.grid(row= 8, column = 5)
game2_button["command"] = lambda: messagebox.showinfo("Sorry!", "Incorrect Answer!")
game3_button = tk.Button(my_canvas1, text = "Red")
game3_button.grid(row= 8, column = 7)
game3_button["command"] = lambda: messagebox.showinfo("Sorry", "Incorrect Answer!")
circle_button = tk.Button(win, text="New Game")
circle_button.pack()
circle_button["command"] = drawCircle
quit_button = tk.Button(win, text="Quit")
quit_button.pack()
quit_button['command'] = win.destroy
You are using both the create_... methods and grid methods on your canvas object. It won't behave as you expected.
To achieve what you want, you can create a Frame, put your buttons in it, and then use create_window method on your canvas:
def drawCircle():
...
shape = my_canvas1.create_oval(x1, y1, x1 + diameter, y1 + diameter+20, fill=color)
frame = tk.Frame(my_canvas1)
game1_button = tk.Button(frame, text = "Green")
game1_button.grid(row= 8, column = 3)
game1_button["command"] = lambda: messagebox.showinfo("Congratulations!", "Correct Answer!")
game2_button = tk.Button(frame, text = "Blue")
game2_button.grid(row= 8, column = 5)
game2_button["command"] = lambda: messagebox.showinfo("Sorry!", "Incorrect Answer!")
game3_button = tk.Button(frame, text = "Red")
game3_button.grid(row= 8, column = 7)
game3_button["command"] = lambda: messagebox.showinfo("Sorry", "Incorrect Answer!")
my_canvas1.create_window(200,500,window=frame)
And of course, add win.mainloop() to the bottom of your program if you haven't already.
Having taken a class on C and Assembly, I decided I wanted to learn python. This is my first attempt with it so please excuse any abuse of notation.
The following is the portion of my GUI I have created to control a grow cycle. I am having an issue with the use IntVar. I have created a countdown to the end-of-cycle (see countdown method). I have tried including the self.remaining label within Countdown, it does show the countdown but the label showing the countdown does not destroy with the cancel button and time_state label at end-of-cycle. With the self.remaining label put in the init using IntVar, it shows the initial countdown in days,hours, mins,secs but stays with that initial value and does not update as countdown is repeated. Shouldn't using IntVar this way update the day_val, hour_val, min_val, and sec_val in the self.remaining label?
from tkinter import *
import time
class Grow_Cycle():
def __init__(self):
self.time_state = Label(app, text = ("You have selected:\n "+str(day_val)+" days,\n "+str(hour_val)+" hours,\n "+str(min_val)+" minutes"))
self.time_state.grid(column = 1, row =1)
self.bttn_3 = Button(app, text = "Cancel", command = self.cancel)
self.bttn_3.configure(bg = "red")
self.bttn_3.grid(row = 2, column = 0,
ipadx = 50, ipady = 20,
padx = 20, pady = 100)
self.temp1 = (day_val * 86400)+(hour_val * 3600)+(min_val * 60)
self.countdown()
self.remaining = Label(app, text = ("You have remaining:\n"+str(day_lef.get())+" days,\n"
+str(hour_lef.get())+" hours,\n"
+str(min_lef.get())+" minutes,\n"
+str(sec_lef.get())+" seconds"),
textvariable = (day_lef, hour_lef, min_lef, sec_lef))
self.remaining.grid(column = 1, row = 2)
def cancel(self):
self.remaining.destroy()
self.bttn_3.destroy()
self.time_state.destroy()
self.temp1 = 0
def countdown(self):
if self.temp1 > 0:
self.temp1 -= 1
global day_lef
day_lef = IntVar()
day_lef.set(int(self.temp1 / 86400))
day_rem = self.temp1 % 86400
global hour_lef
hour_lef = IntVar()
hour_lef.set(int(day_rem / 3600))
hour_rem = day_rem % 3600
global min_lef
min_lef = IntVar()
min_lef.set(int(hour_rem / 60))
min_rem = hour_rem % 60
global sec_lef
sec_lef = IntVar()
sec_lef.set(int(min_rem % 60))
clock.after(1000, self.countdown)
else:
self.cancel()
def tick():
global time1
time2 = time.strftime('%H:%M:%S\n\n%m-%d-%Y')
if time2 != time1:
time1 = time2
clock.config(text=time2)
clock.after(200, tick)
def grow():
# Grow Time set window
set_grow_time = Toplevel(app)
set_grow_time.title("Enter Cycle Time")
set_grow_time.geometry("240x400")
# Set Day
set_day_grow = Spinbox(set_grow_time, from_ = 0, to_ = 10)
set_day_grow.grid(column = 0, row =0,
padx = 20, pady = 20)
set_day_grow.config(width = 10)
set_day_lbl = Label(set_grow_time, text = "Set Day: 0 to 10")
set_day_lbl.grid(column = 1, row = 0)
# Set Hour
set_hour_grow = Spinbox(set_grow_time, from_ = 0, to_ = 23)
set_hour_grow.grid(column = 0, row = 1,
padx = 20, pady = 20)
set_hour_grow.config(width = 10)
set_hour_lbl = Label(set_grow_time, text = "Set Hour: 0 to 23")
set_hour_lbl.grid(column = 1, row = 1)
# Set Minute
set_min_grow = Spinbox(set_grow_time, from_ = 0, to_ = 59)
set_min_grow.grid(column = 0, row = 2,
padx = 20, pady = 20)
set_min_grow.config(width = 10)
set_hour_lbl = Label(set_grow_time, text = "Set Minute: 0 to 59")
set_hour_lbl.grid(column = 1, row = 2)
# Confirm Selection
def fetch_time():
global day_val
day_val = int(set_day_grow.get())
global hour_val
hour_val = int(set_hour_grow.get())
global min_val
min_val = int(set_min_grow.get())
Grow_Cycle()
confirm_grow_time = Button(set_grow_time, text = "OK", command = lambda: (fetch_time(), set_grow_time.destroy()))
confirm_grow_time.grid(column = 0, row = 3)
cancel_grow_time = Button(set_grow_time, text = "Cancel", command = set_grow_time.destroy)
cancel_grow_time.grid(column = 1, row = 3)
def clean():
pass # will finish later
def cancel():
pass # will finish later
# main
# Main Loop Begin
root = Tk()
root.title("Grower")
root.geometry("1280x720")
app = Frame(root)
app.grid()
time1 = ''
clock = Label(app)
clock.config(font = ("Impact", 20))
clock.grid(column = 1, row = 0,
ipadx = 20, ipady = 20,
padx = 50, pady =50)
# Button Setup
bttn_1 = Button(app, text = "Grow", command = grow)
bttn_1.configure(bg = "light green")
bttn_1.grid(column = 0, row = 0,
ipadx = 50, ipady = 20,
padx = 20, pady = 100)
bttn_2 = Button(app, text = "Clean", command = clean)
bttn_2.configure(bg = "cyan")
bttn_2.grid(row = 1, column = 0,
ipadx = 50, ipady = 20,
padx = 20, pady = 100)
tick()
root.mainloop()
After reviewing your code I have made some changes to how you are formatting your strings and how you are apply strings to your labels.
Your class def was not working for me so I rewrote it with a standard method I use to create a tkinter class.
I don't believe you can have both a text = "string' and a textvariable = StringVar() at the same time. So I have formatted the data into your string var.
The way you had it set up it would not work for a few reason. You cant use multiple IntVar()'s in a textvariable so you need to first combine them into a string variable and then use that as your update method to the label.
I have changed your string concatination removing the + method and using format() instead as this is the correct method currently.
I also added self.string_var.set to your countdown() method so we can keep this updated with the countdown.
Take a look at this code and let me know if you have any question.
import tkinter as tk
class Grow_Cycle(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.day_val = 1
self.hour_val = 0
self.min_val = 0
self.day_lef = tk.IntVar()
self.day_lef.set(1)
self.hour_lef = tk.IntVar()
self.hour_lef.set(0)
self.min_lef = tk.IntVar()
self.min_lef.set(0)
self.sec_lef = tk.IntVar()
self.sec_lef.set(0)
self.day_rem = 0
self.hour_rem = 0
self.min_rem = 0
self.string_var = tk.StringVar()
self.string_var2 = tk.StringVar()
self.string_var.set(
"You have remaining:\n{} days,\n{} hours,\n{} minutes,\n{} seconds".format(self.day_lef.get(), self.hour_lef.get(), self.min_lef.get(), self.sec_lef.get()))
self.time_state = tk.Label(self.master, text = (
"You have selected:\n {} days,\n {} hours,\n {} minutes").format(self.day_val, self.hour_val, self.min_val))
self.time_state.grid(row = 1, column = 1)
self.bttn_3 = tk.Button(self.master, bg = "red", text = "Cancel", command = self.cancel)
self.bttn_3.grid(row = 2, column = 0, ipadx = 50, ipady = 20, padx = 20, pady = 100)
self.temp1 = (self.day_val * 86400)+(self.hour_val * 3600)+(self.min_val * 60)
self.remaining = tk.Label(self.master, textvariable = self.string_var)
self.remaining.grid(column = 1, row = 2)
self.countdown()
def cancel(self):
self.remaining.destroy()
self.bttn_3.destroy()
self.time_state.destroy()
self.temp1 = 0
def countdown(self):
self.string_var.set(
"You have remaining:\n{} days,\n{} hours,\n{} minutes,\n{} seconds".format(self.day_lef.get(), self.hour_lef.get(), self.min_lef.get(), self.sec_lef.get()))
if self.temp1 > 0:
self.temp1 -= 1
self.day_lef.set(int(self.temp1 / 86400))
self.day_rem = self.temp1 % 86400
self.hour_lef.set(int(self.day_rem / 3600))
self.hour_rem = self.day_rem % 3600
self.min_lef.set(int(self.hour_rem / 60))
self.min_rem = self.hour_rem % 60
self.sec_lef.set(int(self.min_rem % 60))
root.after(1000, self.countdown)
else:
self.cancel()
if __name__ == "__main__":
root = tk.Tk()
Grow_Cycle(root)
root.mainloop()
In the code below I have managed to partially validate the data entered into the self.e2 entry widget, unfortunately if the entry widget is empty and the Submit button is pressed then a ValueError is generated" ValueError: invalid literal for int() with base 10: '' "
I would like to have the program recognise that the e2 entry widget is empty, have the ValueError trapped and return focus back to the entry widget.
I have attempted to do this using the is_valid_int and invalid_int methods but this is not working.
from tkinter import *
from tkinter import ttk
from tkinter.scrolledtext import *
class DailyOrderGUI:
def __init__(self, parent):
#Data entring frame
self.frame = Frame(parent, bg = "grey")
self.frame.grid(row=0)
self.label1 = Label(self.frame, text = "Mrs CackleBerry's Egg Ordering System", wraplength = 200, bg="grey", font=("Comic Sans MS", "14", "bold"))
self.label1.grid(row = 0, columnspan = 3, padx = 5, pady = 5)
self.superegg_img = PhotoImage(file = "images/superegg.gif")
self.label5 = Label(self.frame, bg="grey", image = self.superegg_img)
self.label5.grid(row = 0, column= 3, padx = 5, pady = 5)
self.v = StringVar()
self.v.set("Monday")
self.label2 = Label(self.frame, text = "Which day are you ordering for?", bg="grey", font=("Arial", "12", "bold"))
self.label2.grid(row = 1, columnspan = 4, sticky = W)
self.rb1 = Radiobutton(self.frame, variable = self.v, bg="grey", value = "Monday", text = "Monday")
self.rb1.grid(row = 2, column = 0, sticky = W)
self.rb2 = Radiobutton(self.frame, variable = self.v, bg="grey", value = "Tuesday", text = "Tuesday")
self.rb2.grid(row = 2, column = 1, sticky = W)
self.rb3 = Radiobutton(self.frame, variable = self.v, bg="grey", value = "Wednesday", text = "Wednesday")
self.rb3.grid(row = 2, column = 2, sticky = W)
self.rb4 = Radiobutton(self.frame, variable = self.v, bg="grey", value = "Thursday", text = "Thursday")
self.rb4.grid(row = 2, column = 3, sticky = W)
self.rb5 = Radiobutton(self.frame, variable = self.v, bg="grey", value = "Friday", text = "Friday")
self.rb5.grid(row = 3, column = 0, sticky = W)
self.rb6 = Radiobutton(self.frame, variable = self.v, bg="grey", value = "Saturday", text = "Saturday")
self.rb6.grid(row = 3, column = 1, sticky = W)
self.rb7 = Radiobutton(self.frame, variable = self.v, bg="grey", value = "Sunday", text = "Sunday")
self.rb7.grid(row = 3, column = 2, sticky = W)
self.label3 = Label(self.frame, text = "Customer's Name:?(Press \"Orders Complete\" to finish)", bg="grey", font=("Arial", "12", "bold"))
self.label3.grid(row = 4, columnspan = 4,padx = 5, sticky = W)
self.e1 = Entry(self.frame, width = 30)
self.e1.grid(row = 5, columnspan = 4, sticky = W, padx = 5, pady=3)
self.e1.focus()
self.label4 = Label(self.frame, text = "How many eggs being ordered:?", bg="grey", font=("Arial", "12", "bold"))
self.label4.grid(row = 6, columnspan = 4,padx = 5,sticky = W)
integer = self.create_integer_widget()
self.btn1 = Button(self.frame, text = "Submit")
self.btn1.grid(row = 8, padx = 5, pady = 3, sticky = E+W)
self.btn1.bind("<Button-1>", self.get_orders)
self.btn2 = Button(self.frame, text = "Orders Complete", command = self.show_summary_result)
self.btn2.grid(row = 8, column = 3, padx = 5, pady = 3, sticky = E+W)
#Summary Frame
self.summ_frame = Frame(parent, bg = "grey")
self.summ_frame.grid(row=0)
self.summ_label1 = Label(self.summ_frame, text = "Mrs CackleBerry's Egg Ordering System", bg="grey", font=("Comic Sans MS", "14", "bold"))
self.summ_label1.grid(row = 0, columnspan = 4, padx = 5, pady = 5)
self.scrolled_display = ScrolledText(self.summ_frame, width = 50, height = 10, bg="thistle", font=("Times New Roman", "12"))
self.scrolled_display.grid(row = 1, columnspan = 2, padx = 5, pady = 20, sticky = W)
self.data_entry_btn = Button(self.summ_frame, text = "Back to Data Entry", command = self.show_data_entry_frame)
self.data_entry_btn.grid(row = 2, column = 0, sticky = SE, padx = 5, pady = 20)
self.egg_orders=[]
self.show_data_entry_frame()
def create_integer_widget(self):
self.e2 = ttk.Entry(self.frame, width = 10, validate='key')
self.e2['validatecommand'] = (self.frame.register(self.is_valid_int), '%P')
self.e2['invalidcommand'] = (self.frame.register(self.invalid_int), '%W')
self.e2.grid(row = 7, columnspan = 4, sticky = W, padx = 5, pady=3)
self.e2.bind("<Return>", self.get_orders)
return self.e2
def is_valid_int(self, txt):
# txt - value in %P
if not txt: # do not accept empty string
return False
try:
int(txt)
return True # accept integer
except ValueError: # not an integer
return False
def invalid_int(self, widgetName):
# called automatically when the
# validation command returns 'False'
# get entry widget
widget = self.frame.nametowidget(widgetName)
# clear entry
widget.delete(0, END)
# return focus to integer entry
widget.focus_set()
widget.bell()
def show_data_entry_frame(self):
self.summ_frame.grid_remove()
self.frame.grid()
root.update_idletasks()
def show_summary_result(self):
self.frame.grid_remove()
self.summ_frame.grid()
root.update_idletasks()
self.scrolled_display.delete('1.0', END)
if len(self.egg_orders) == 0:
self.scrolled_display.insert(END, "No Orders")
else:
total = 0
self.scrolled_display.insert(END, "Orders for " + self.v.get() + "\n")
for i in range(len(self.egg_orders)):
total += self.egg_orders[i].num_eggs
self.scrolled_display.insert(END, str(self.egg_orders[i]) + "\n")
self.scrolled_display.insert(END, "" + "\n")
self.scrolled_display.insert(END, "Summary for " + self.v.get() + "\n")
self.scrolled_display.insert(END, "" + "\n")
self.scrolled_display.insert(END, "Total eggs: " + str(total) + "\n")
self.scrolled_display.insert(END, "Dozens required: " + str(self.get_dozens(total)) + "\n")
average = 0
if len(self.egg_orders) > 0:
average = total / len(self.egg_orders)
self.scrolled_display.insert(END, "Average number of eggs per customer: {0:.1f}".format(average) + "\n")
def get_orders(self, event):
"""
Collects order information - name, number of eggs in a loop
"""
self.name = self.e1.get()
self.no_eggs = self.e2.get()
self.e1.delete(0, END)
self.e2.delete(0, END)
self.e1.focus()
self.egg_orders.append(EggOrder(self.name, self.no_eggs))
def get_dozens (self, total):
"""
returns whole number of dozens required to meet required number of eggs
"""
num_dozens = total//12
if total%12 != 0:
num_dozens += 1
return num_dozens
class EggOrder:
price_per_doz = 6.5
def __init__(self, name, num_eggs):
self.name = name
self.num_eggs = int(num_eggs)
def calc_price(self):
self.price = EggOrder.price_per_doz/12 * self.num_eggs
return self.price
def __str__(self):
return("{} ordered {} eggs. The price is ${:.2f}".format(self.name, self.num_eggs , self.calc_price()))
#main routine
if __name__== "__main__":
root = Tk()
root.title("Mrs Cackleberry's Egg Ordering Program")
frames = DailyOrderGUI(root)
root.mainloop()
Let's trace through what happens when you click "Submit".
First:
self.btn1 = Button(self.frame, text = "Submit")
self.btn1.grid(row = 8, padx = 5, pady = 3, sticky = E+W)
self.btn1.bind("<Button-1>", self.get_orders)
So, it calls self.get_orders. And the last line in that function is:
self.no_eggs = self.e2.get()
# ...
self.egg_orders.append(EggOrder(self.name, self.no_eggs))
And inside the EggOrder.__init__ function you've got this:
self.num_eggs = int(num_eggs)
That's presumably where the error happens.
(Note that all of that work would have been unnecessary if you'd posted the traceback instead of just the error string.)
So, when self.e2.get() returns an empty string, you end up calling int(''), and that raises a ValueError.
Unless you want to try to check for that possibility in advance (which is rarely a good idea), you will need a try:/except ValueError: somewhere. The question is, where? Well, that depends on what you want to do.
If you want to create an empty EggOrder, you'd do it inside EggOrder.__init__:
try:
self.num_eggs = int(num_eggs)
except ValueError:
self.num_eggs = 0
On the other hand, if you want to not create an EggOrder at all, you'd do it inside self.get_orders:
try:
order = EggOrder(self.name, self.no_eggs)
except ValueError:
# pop up an error message, log something, call self.invalid_int, whatever
else:
self.egg_orders.append(order)
Of course you probably want to do this before all the destructive stuff (self.e1.delete(0, END), etc.).
Also, if you wanted to call invalid_int as-is, you'd need to pass it the name of the self.e2 widget. Since you didn't give it one, it'll be something dynamic and unpredictable like .1234567890, which you'll have to ask the widget for, just so you can look the widget back up by that name. It would be simpler to factor out the core functionality, something like this:
def invalid_int(self, widgetName):
widget = self.frame.nametowidget(widgetName)
self.handle_invalid_int(widget)
def handle_invalid_int(self, widget):
widget.delete(0, END)
# etc.
Then you can just call handle_invalid_int(self.e2).
Meanwhile, you've tried adding a validation function to check for a valid int input.
is_valid_int should work fine. However, the if not txt part is completely unnecessary—int(txt) will already handle an empty string the same way it handles a non-numeric string.
And the way you've hooked it up should work too.
However, a validation function runs on each edit to the Entry widget, not when you click some other random widget elsewhere. For example, if you try typing letters into the Entry, it should erase them as soon as you can type them. (Note that if you type 123a456 you'll end up with 456, not 123456, which may or may not be what you want…)
So, you've done that right, but it's not what you wanted to do.