This question already has answers here:
Tkinter: AttributeError: NoneType object has no attribute <attribute name>
(4 answers)
Closed 7 years ago.
So I'm trying to write some code that takes two user inputted values, compares them, and then tells the user if he/she input the correct values. (USL should be greater than LSL, pretty simple criteria) Here you can see how i get the entry from the user
# get USL and LSL
Label(self, text = "USL").grid(row = 5, column = 0, sticky = W, padx=5, pady=5)
self.e6 = Entry(self).grid(row = 5, column = 1)
Label(self, text = "LSL").grid(row = 6, column = 0, sticky = W, padx=5, pady=5)
self.e7 = Entry(self).grid(row = 6, column = 1)
This next bit of code is where I check the code
# button to check USL is higher than LSL
self.button = Button(self)
self.button["text"] = "Check USL and LSL"
self.button["command"] = self.check_limits
self.button.grid(row = 6, column = 2, sticky = W, padx=5, pady=5)
and finally here is where i define the check_limits function
def check_limits(self):
e6 = int(self.e6)
e7 = int(self.e7)
if e6 > e7:
message = "Limits are good"
else:
message = "USL can't be less than LSL, please re-enter USL and LSL"
self.text.delete(0.0, END)
self.text.insert(0.0, message)
Currently I have the variables e6 and e7 forced as integers, but when i do this I get an error saying that int() arguments must be a string or a number, not "NoneType". Why would they be defined as null and not integers in this case?
Alternatively, if I don't force them to be integers then I always get "USL can't be less than...", regardless of whether e6 is greater or less than e7. Why is it doing this and how do I fix it?
Here's my entire code if any else of it is needed for the context of this question
from Tkinter import *
class Application(Frame):
""" A SPC program that takes user input and saves the file """
def __init__(self,master):
""" initializes the frame """
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
"""create widgets for user inputted data"""
# get name
Label(self, text = "First Name").grid(row = 0, column = 0, sticky = W, padx=5, pady=5)
self.e1 = Entry(self).grid(row = 0, column = 1)
Label(self, text = "Last Name").grid(row = 1, column = 0, sticky = W, padx=5, pady=5)
self.e2 = Entry(self).grid(row = 1, column = 1)
# get work order
Label(self, text = "Work Order Number").grid(row = 2, column = 0, sticky = W, padx=5, pady=5)
self.e3 = Entry(self).grid(row = 2, column = 1)
# get todays date
Label(self, text = "Todays Date").grid(row = 3, column = 0, sticky = W, padx=5, pady=5)
self.e4 = Entry(self).grid(row = 3, column = 1)
# get bubble number
Label(self, text = "Bubble Number").grid(row = 4, column = 0, sticky = W, padx=5, pady=5)
self.e5 = Entry(self).grid(row = 4, column = 1)
# get USL and LSL
Label(self, text = "USL").grid(row = 5, column = 0, sticky = W, padx=5, pady=5)
self.e6 = Entry(self).grid(row = 5, column = 1)
Label(self, text = "LSL").grid(row = 6, column = 0, sticky = W, padx=5, pady=5)
self.e7 = Entry(self).grid(row = 6, column = 1)
# button to check USL is higher than LSL
self.button = Button(self)
self.button["text"] = "Check USL and LSL"
self.button["command"] = self.check_limits
self.button.grid(row = 6, column = 2, sticky = W, padx=5, pady=5)
# creates a spot to dictate whether USL and LSL are correct
self.text = Text(self, width = 35, height = 2, wrap = WORD)
self.text.grid(row = 6, column = 3, sticky = W)
def check_limits(self):
e6 = int(self.e6)
e7 = int(self.e7)
if e6 > e7:
message = "Limits are good"
else:
message = "USL can't be less than LSL, please re-enter USL and LSL"
self.text.delete(0.0, END)
self.text.insert(0.0, message)
root = Tk()
root.title("SPC Input Program")
root.geometry("700x500")
app = Application(root)
root.mainloop()
Your issue is this
self.e6 = Entry(self).grid(row = 5, column = 1)
When you want to use an assigned widget you can't pack, place or grid it. You need to change it to this
self.e6 = Entry(self)
self.e6.grid(row = 5, column = 1)
You are getting NoneType because of the assignment with the grid. Doing that is fine for static widgets like Labels and Buttons you don't plan on accessing. But when you want to get or assign a value to the widget, it needs to be the widget itself and not widget.grid()
Related
In an UI I'm trying to make I want to make an interactive matrix where some data should be entered by the user. Apart from the data value Entries, each column and row has an Entry with a placeholder text where the user should enter the name of the column (e.g. price) and row (e.g. a company). In order to generate the matrix I have this class method
import customtkinter as ctk
from tkinter import messagebox
class AK_Matrix_Frame(ctk.CTkFrame):
def __init__(self, root):
#initialize the frame class-------------------
super().__init__(root)
#Set frame properties-------------------------
self.width = 200
self.height = 400
self.grid(row=1, column=0, padx=10, pady=5)
#Parameters-----------------------------------
self.m = 2
self.n = 3
#initialize and set up reference dict----------
self.AK_Widget_Matrix_dict ={}
self.gen_matrix()
#Base Button-----------------------------------
read_button = ctk.CTkButton(self, text = "Read", command = self.read_matrix)
read_button.pack(pady=12, padx = 10)
root.mainloop()
def gen_matrix(self):
matrix_label = ctk.CTkLabel(self, text = "Anbieter-Kategorien Matrix", font= ('Ariel', 18))
matrix_label.pack(pady=12, padx = 10)
self.matrix_frame = ctk.CTkFrame(self, width=200, height=200)
self.matrix_frame.pack( padx=10, pady=5, expand=True)
self.AK_Widget_Matrix_dict[(0,0)] = ctk.CTkLabel(self.matrix_frame, text = "A\K", font= ('Ariel', 14))
self.AK_Widget_Matrix_dict[(0,0)].grid(row = 0, column = 0, padx = 5, pady = 5, sticky='w'+'e'+'n'+'s')
for i in range(self.m):
self.AK_Widget_Matrix_dict[(i+1,0)] = ctk.CTkEntry(self.matrix_frame, placeholder_text = "Anbieter{a}".format(a = i+1), font= ('Ariel', 14))
self.AK_Widget_Matrix_dict[(i+1,0)].grid(row = i+1, column = 0, padx = 5, pady = 5, sticky='w'+'e'+'n'+'s')
self.AK_Widget_Matrix_dict[(i+1,0)].bind("<Return>", self.replace_matrix_entry_w_label)
for j in range(self.n):
if i == 0:
self.AK_Widget_Matrix_dict[(0,j+1)] = ctk.CTkEntry(self.matrix_frame, placeholder_text = "Kategorie{k}".format(k = j+1), font= ('Ariel', 14))
self.AK_Widget_Matrix_dict[(0,j+1)].grid(row = 0, column = j+1, padx = 5, pady = 5, sticky='w'+'e'+'n'+'s')
self.AK_Widget_Matrix_dict[(0,j+1)].bind("<Return>", self.replace_matrix_entry_w_label)
self.AK_Widget_Matrix_dict[(i+1,j+1)] = ctk.CTkEntry(self.matrix_frame, font= ('Ariel', 14))
self.AK_Widget_Matrix_dict[(i+1,j+1)].grid(row = i+1, column = j+1, padx = 5, pady = 5, sticky='w'+'e'+'n'+'s')
def read_matrix(self):
pass
def replace_matrix_entry_w_label(self, event):
print(event.widget.grid_info())
i = event.widget.grid_info()["row"]
j = event.widget.grid_info()["column"]
print(event.widget)
print("Row, Column:",i,j)
txt = event.widget.get()
print("Event widget contains:",txt)
event.widget.destroy()
self.AK_Widget_Matrix_dict[(i , j)] = ctk.CTkLabel(self.matrix_frame, text = txt, font= ('Ariel', 14))
self.AK_Widget_Matrix_dict[(i , j)].grid(row = i, column = j, padx = 5, pady = 5, sticky='w'+'e'+'n'+'s')
AK_Matrix_Frame(ctk.CTk())
The matrix displays without problem and all entries and labels are placed in the correct location. But when the class method self.replace_matrix_entry_w_label is called, the grid information is transmitted falsely.
And this is the output for any fringe column or row entry I enter text and press return:
{'in': <customtkinter.windows.widgets.ctk_entry.CTkEntry object .!ak_matrix_frame.!ctkframe.!ctkentry2>, 'column': 0, 'row': 0, 'columnspan': 1, 'rowspan': 1, 'ipadx': 0, 'ipady': 0, 'padx': 6, 'pady': (2, 3), 'sticky': 'nesw'}
Row, Column: 0 0
Event widget contains: 23def
So the text one writes in is read correctly, but the row and column are wrong (always 0,0 no matter where the widget is located).
I had the code almost identically with tkinter instead off customtkinter, and then it worked.
Why is the row and column in grid_info() not correct?
I tried accessing the bound widgets event.widget.grid_info() in order to get row and column position and use that to replace the Entry with a Label. What actually happens is that the row and column values are always 0,0, no matter which entry in the matrix I select. Since the text written in the Entry is actually correct I don't understand where the problem is.
The problem arises because customtkinter's CTkEntry widget internally creates a tkinter Entry as an attribute of itself.
event.widget does not refer to the CTkEntry that has a row and column number in your grid, but instead refers to the Entry that this CTkEntry contains.
Your code in the function replace_matrix_entry_w_label asks for the position of Entry in its parent. But the parent is a CTkEntry widget and the only location is (0,0). The code works if you ask for the grid_info() of the master of Entry, that is of CTkEntry. Like this:
def replace_matrix_entry_w_label(self, event):
i = event.widget.master.grid_info()["row"]
j = event.widget.master.grid_info()["column"]
print("Row, Column:",i,j)
txt = event.widget.get()
event.widget.master.destroy()
self.AK_Widget_Matrix_dict[(i , j)] = ctk.CTkLabel(self.matrix_frame, text = txt, font= ('Ariel', 14))
self.AK_Widget_Matrix_dict[(i , j)].grid(row = i, column = j, padx = 5, pady = 5, sticky='w'+'e'+'n'+'s')
For the same reason as described above, if you don't destroy event.widget.master you will still have an empty shell of CTkEntry where just the internal Entry is destroyed, but the object still lives on your UI.
I have created two check buttons and I want them both in row 5 and column 1. But I want the 'YES' check button to be formatted on the left side and the 'NO' check button to be on the right.
I have used sticky but it will not work. I also tried changing the width of the check buttons but this did not work either.
Do you have any suggestions?
Thank you!
self.yes_checkbtn = Checkbutton(self.entry_frame, width = 20, variable = checkbutton1,
anchor = W, text = "YES")
self.no_checkbtn = Checkbutton(self.entry_frame, width = 20, variable = checkbutton2,
anchor = E, text = "NO")
self.yes_checkbtn.grid(row = 5, column = 1, sticky = W)
self.no_checkbtn.grid(row = 5, column = 1, sticky = E)
Outcome:
I dont have enough points yet to show the image without a link - sorry
Put both check buttons under another new frame, then you can grid this new frame to column 1 of row 5, also change option width of Checkbutton to a small number, like 5.
Example code
from tkinter import *
root = Tk()
entry_frame = Frame(root)
entry_frame.grid(row=0, column=0)
for i in range(4):
for j in range(2):
label = Label(entry_frame, text=f'Row {i} Column {j}')
label.grid(row=i, column=j)
frame = Frame(entry_frame)
frame.grid(row=4, column=1)
checkbutton1 = BooleanVar()
checkbutton2 = BooleanVar()
yes_checkbtn = Checkbutton(frame, width = 5, variable = checkbutton1,
anchor = W, text = "YES", bg='green')
no_checkbtn = Checkbutton(frame, width = 5, variable = checkbutton2,
anchor = E, text = "NO", bg='blue')
yes_checkbtn.grid(row = 0, column = 1, sticky = W)
no_checkbtn.grid(row = 0, column = 2, sticky = W)
root.mainloop()
I want to make a button in my widget that when I press it, it proceeds to the next lines of code, and closes the existing widget of where the button is.
from tkinter import *
root = Tk()
Label(root, text = "Childs First name").grid(row = 0, sticky = W)
Label(root, text = "Childs Surname").grid(row = 1, sticky = W)
Label(root, text = "Childs Year of Birth").grid(row = 2, sticky = W)
Label(root, text = "Childs Month of Birth").grid(row = 3, sticky = W)
Label(root, text = "Childs Day of Birth").grid(row = 4, sticky = W)
Fname = Entry(root)
Sname = Entry(root)
x = Entry(root)
y = Entry(root)
z = Entry(root)
Fname.grid(row = 0, column = 1)
Sname.grid(row = 1, column = 1)
x.grid(row = 3, column = 1)
y.grid(row = 2, column = 1)
z.grid(row = 4, column = 1)
button1 = Button(root, text = "Quit", command = root.quit, bg = "Grey", fg = "White", width = 12).grid(row = 5, column = 0, sticky = W)
def save():
Fname_value = Fname.get()
Sname_value = Sname.get()
x_value = x.get()
y_value = y.get()
z_value = z.get()
save()
mainloop()
Sorry for this late answer. To make a 'continue' button:
continue_button = tk.Button(frame,text='continue',command=func)
continue_button.config(width=width_continue_button)
# set the coordinates as you want. here 2,6 for the example
continue_button.grid(row=2,column=6,padx=width_continue_grid)
then you have to write the function 'func' to fulfill your needs.
hope this helps
So several other people have asked similar questions, but I don't think they necessarily apply to my situation. I'm writing a program that's eventually going to take user inputs, check to make sure they correct, and then save it all to a file. Currently, I'm passing all the user inputs to a text box in tkinter so that I can just save whatever's in that one text box. I'm using a for loop to go through the 7 user entry fields I have and then insert them into the text box.
def submit(self):
""" submits user data up to input data section of GUI and checks USL vs LSL"""
e6 = IntVar(self.e6)
e7 = IntVar(self.e7)
if e6 > e7:
message = "Limits are good"
else:
message = "USL can't be less than LSL, please re-enter USL and LSL"
self.checklimits.delete(0.0, END)
self.checklimits.insert(0.0, message)
x = 1
for x in range (1, 8):
xname = "self.e" + str(x)
entry = xname.get()
if entry:
self.checktext.insert(END, entry + "\n")
x = x+1
I'm wanting to take the x value in the for loop and eventually end up with something like "self.e#.get()" since that's how i've defined the user entries, see example below:
def create_widgets(self):
"""create widgets for user inputted data"""
# creates a text widget next to the entries that displays what the user has input
Label(self, text = "Do these values you entered seem correct?").grid(row = 0, column = 4, sticky = W, padx = 5, pady = 5)
self.checktext = Text(self, width =15, height = 42, wrap = WORD)
self.checktext.grid(row = 1, rowspan = 10, column = 4, sticky = W, padx =5, pady =5)
# get name
Label(self, text = "First Name:").grid(row = 0, column = 0, sticky = W, padx=5, pady=5)
self.e1 = Entry(self)
self.e1.grid(row = 0, column = 1)
Label(self, text = "Last Name:").grid(row = 1, column = 0, sticky = W, padx=5, pady=5)
self.e2 = Entry(self)
self.e2.grid(row = 1, column = 1)
Right now though, python isn't recognizing that entry as an entry and is saying back to me that "'str' object has no attribute 'get'"
So for one, why can't I "get" a string value, and two, how can I get python to recognize my previously defined entry? Here's my entire code for context
from Tkinter import *
class Application(Frame):
""" A SPC program that takes user input and saves the file """
def __init__(self,master):
""" initializes the frame """
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
"""create widgets for user inputted data"""
# creates a text widget next to the entries that displays what the user has input
Label(self, text = "Do these values you entered seem correct?").grid(row = 0, column = 4, sticky = W, padx = 5, pady = 5)
self.checktext = Text(self, width =15, height = 42, wrap = WORD)
self.checktext.grid(row = 1, rowspan = 10, column = 4, sticky = W, padx =5, pady =5)
# get name
Label(self, text = "First Name:").grid(row = 0, column = 0, sticky = W, padx=5, pady=5)
self.e1 = Entry(self)
self.e1.grid(row = 0, column = 1)
Label(self, text = "Last Name:").grid(row = 1, column = 0, sticky = W, padx=5, pady=5)
self.e2 = Entry(self)
self.e2.grid(row = 1, column = 1)
# get work order
Label(self, text = "Work Order Number:").grid(row = 2, column = 0, sticky = W, padx=5, pady=5)
self.e3 = Entry(self)
self.e3.grid(row = 2, column = 1)
# get todays date
Label(self, text = "Todays Date:").grid(row = 3, column = 0, sticky = W, padx=5, pady=5)
self.e4 = Entry(self)
self.e4.grid(row = 3, column = 1)
# get bubble number
Label(self, text = "Bubble Number:").grid(row = 4, column = 0, sticky = W, padx=5, pady=5)
self.e5 = Entry(self)
self.e5.grid(row = 4, column = 1)
# get USL and LSL
Label(self, text = "USL:").grid(row = 5, column = 0, sticky = W, padx=5, pady=5)
self.e6 = Entry(self)
self.e6.grid(row = 5, column = 1)
Label(self, text = "LSL:").grid(row = 6, column = 0, sticky = W, padx=5, pady=5)
self.e7 = Entry(self)
self.e7.grid(row = 6, column = 1)
"""# button to check USL is higher than LSL
self.button7 = Button(self)
self.button7["text"] = "Check Limits"
self.button7["command"] = self.check_limits
self.button7.grid(row = 6, column = 2, sticky = W, padx=5, pady=5)"""
# button to submit user entered values up to the input data values portion of the gui
self.button6 = Button(self)
self.button6["text"] = "Submit"
self.button6["command"] = self.submit
self.button6.grid(row = 5, column = 2, sticky = W, padx=5, pady=5)
# creates a spot to dictate whether USL and LSL are correct
self.checklimits = Text(self, width = 20, height = 2, wrap = WORD)
self.checklimits.grid(row = 6, column = 3, sticky = W, padx = 5)
""" #adds a scroll bar to the data input text box
scrollbar = Scrollbar(self)
scrollbar.pack(side=RIGHT, fill=Y) """
# get User Input Data values
Label(self, text = "Enter Results:").grid(row = 7, column = 0, sticky = W, padx=5, pady=5)
self.e8 = Text(self, width = 15, height = 30)
self.e8.grid(row = 7, column = 1)
""" def check_limits(self):
checks to see if the USL is greater than the LSL
e6 = IntVar(self.e6)
e7 = IntVar(self.e7)
if e6 > e7:
message = "Limits are good"
else:
message = "USL can't be less than LSL, please re-enter USL and LSL"
self.checklimits.delete(0.0, END)
self.checklimits.insert(0.0, message)"""
def submit(self):
""" submits user data up to input data section of GUI and checks USL vs LSL"""
e6 = IntVar(self.e6)
e7 = IntVar(self.e7)
if e6 > e7:
message = "Limits are good"
else:
message = "USL can't be less than LSL, please re-enter USL and LSL"
self.checklimits.delete(0.0, END)
self.checklimits.insert(0.0, message)
x = 1
for x in range (1, 8):
xname = "self.e" + str(x)
entry = xname.get()
if entry:
self.checktext.insert(END, entry + "\n")
x = x+1
root = Tk()
root.title("SPC Input Program")
root.geometry("700x750")
app = Application(root)
root.mainloop()
It appears that you are assuming that e6 = IntVar(self.e6) is getting the value of an entry widget and converting it to an int. That's not what it's doing. It is creating a new variable named e6 that is initialized to zero, and has self.e6 as the "master".
If you want to get the values from an entry widget and convert it to an integer, use the get method on the entry widget.
e6 = int(self.e6.get())
e7 = int(self.e7.get())
The problem that is causing the "str object has no method get" is this code:
xname = "self.e" + str(x)
entry = xname.get()
This is a terrible way to code. As you can see, it won't work. You can make it work with tricks, but a good rule of thumb is to never try to dynamically create variables like this.
If you want to loop over the entry widgets, put them in a list or tuple. For example:
for widget in (self.e1, self.e2, self.e3, self.e4,
self.e5, self.e6, self.e7, self.e8):
entry = widget.get()
if entry:
self.checktext.insert(END, entry + "\n")
...
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.