I am making a restaurant menu program that pulls items and their attributes from a .json file, and then allows users to add them to a cart and export an order (not for actual use, just a project)
A goal I set for this project is to have the menu customizable by simply changing the items in the .json file, and then the program automatically displays them (I want it to be as modular as possible)
I have already worked through problems such as button events and creating the buttons, but I have a problem with the cart buttons. I have "+" and "-" buttons next to each item with a label displaying the selected quantity. Here is an image
I need the label in the middle to change as the +/- buttons are pressed, but since it was created with a for loop I don't know how to go about this. An idea I had was to re-print the entire screen every time a button is pressed, and this should work fine for something this simple, but I think that would be a bad idea because that would not be an acceptable solution to something bigger. I would like to know if there is a better way of doing this, and how I could implement it.
A section of my code is below (with all the fancy colors and images removed), it doesn't look very nice but it can reproduce the problem well enough. I also copied a few lines from the menu.json into the code so that it can run on its own.
import tkinter as tk
menu={ # this is a sample of some items from the .json with the full menu
"Entrees":{
"Huitres":{
"price":24,
"calories":350
},
"Poireaux vinaigrette":{
"price":18,
"calories":430
}
},
"Appetizers":{
"Croissant Basket":{
"price":4,
"calories":600,
"size":"3 Servings (150g)",
"description":"3 fresh croissants to share"
},
"Toasted Baguette":{
"price":4,
"calories":680,
"size":"Several Servings (250g)",
"description":"a warm parisian baguette for the table"
}
}
}
#----------#
window=tk.Tk()
window.geometry("600x720+450+50")
window.minsize(width=600, height=720)
window.maxsize(width=600, height=720)
window.columnconfigure(0, minsize=600)
#----------#
color0="#fff0d4"
color1="#ffe6c4"
color2="#ffb65c"
color3="#ffce8f"
font0="Brush Script MT",
font1="Freestyle Script"
#----------#
class Cart:
def __init__(self):
self.items={} # create empty dict
for catName in menu:
self.items[catName]=menu[catName].copy() # copy category names to new dict
for itemName in menu[catName]:
self.items[catName][itemName]=0 # copy items from categories, set quantities to 0
cart=Cart()
#----------#
def clear(): # destroys all widgets on the screen
for widget in window.winfo_children():
widget.destroy()
def draw_menu_small(category): # this is usually called from a main menu, but this code has been trimmed down
count=0
for itemName in menu[category]:
def changeItemQuantity(category,item,amount):
if amount==1:
cart.items[category][item]+=1
elif amount==-1:
if cart.items[category][item]>0:
cart.items[category][item]-=1
# updateLabel() << This is where I need the Label to be updated
frm_item=tk.Frame( # the frame that holds all of the widgets associated with a given item
window,
relief=tk.FLAT,
width=600,
bg=color0,
)
frm_item.grid(
row=count,
column=0,
sticky="ew",
pady=8
)
frm_item.columnconfigure(0,minsize=450)
frm_item.columnconfigure(1,minsize=150)
tk.Label(
frm_item,
text=itemName,
font=(font1, 26),
bg=color0,
).grid(
row=0,
column=0,
sticky="w",
padx=30,
)
frm_buttons=tk.Frame(
frm_item,
relief=tk.FLAT,
bg=color0,
)
frm_buttons.grid(
row=0,
column=1,
sticky="e",
padx=12
)
tk.Button(
frm_buttons,
text="-",
font=("Arial",16),
bg=color0,
activebackground=color0,
relief=tk.FLAT,
width=2,
command=lambda itemName=itemName:changeItemQuantity(category,itemName,-1),
).grid(
row=0,
column=0,
)
tk.Label(
frm_buttons,
text=cart.items[category][itemName],
font=("Arial",16),
bg=color0,
width=2,
).grid(
row=0,
column=1,
)
tk.Button(
frm_buttons,
text="+",
font=("Arial",16),
bg=color0,
activebackground=color0,
relief=tk.FLAT,
width=2,
command=lambda itemName=itemName:changeItemQuantity(category,itemName,1),
).grid(
row=0,
column=2,
)
price=menu[category][itemName]["price"]
tk.Label(
frm_item,
text=f"${price}",
font=(font1, 16),
bg=color0,
).grid(
row=1,
column=0,
sticky="w",
padx=30,
)
count+=1
#----------#
draw_menu_small("Entrees") # the final product would have a draw_homescreen() here, but the menu_small is the one I have a problem with
window.mainloop()
print(cart.items) # this is for debugging, shows what the buttons are doing
Instead of creating a new label to change the values when + or - is clicked, I suggest you to update the value held by the same Label.
Sample code:
import tkinter as tk
root = tk.Tk()
root.geometry('300x300')
items=['Food a','Food b','food c','food d','food e','food f'] #food items
#a tkinter label object shouldn't be stored as a string, as it loses it's value
#if you print tkinter label object, it looks like this: .!label1 or .!label2 same for button objects: .!button1 then .!button2 etc
#stores the food name as key and tkinter label object(here the label shows the food name) as value like {'food a':<tkinter label object>,'food b':<tkinter label object>}
itemslbl_dict={}
itemlblnum=0
#stores the quantity of each food that the user has added to cart
quantityval_dict={}
for i in items:
quantityval_dict[i]=0 #initially it is 0
#stores the food name as key and tkinter label object as value. Here the label shows the quantity of each food and is placed between the + and - buttons
quantitylbl_dict={}
quantlblnum=0
#stores food name as key and tkinter button object associated with the food name as value.
#i.e. one plus button is associated with increasing the quantity of one food item and not any other food item
plusbtn_dict={}
plusbtnnum=0
#same with the minus button dict
minusbtn_dict={}
minusbtnnum=0
#loop to generate the labels for the items/food names
for i in items:
lbl=tk.Label(root, text=i)
lbl.grid(row=itemlblnum,column=0)
itemlblnum+=1
itemslbl_dict[i] = [lbl]
#this function increase the label showing qauntity by 1 & update the dictionary containing the quantity of each food
#executes whenever a plus button is clicked
def plusbtnclick(item_name):
global quantitylbl_dict, quantityval_dict
lbl = quantitylbl_dict[item_name]
val = int(lbl[0].cget("text"))
lbl[0].config(text=str(val+1)) # increases the value of the variable by 1
quantityval_dict[item_name]=val+1 #updating the dictionary so that it stores the right quantity of each food item
#same fucntion as the above one except that it decreses quantity by 1 and
#executes whenever a minus button is clicked
def minusbtnclick(item_name):
global quantitylbl_dict, quantityval_dict
lbl=quantitylbl_dict[item_name]
val = int(lbl[0].cget("text"))
lbl[0].config(text=str(val-1)) # decreaseses the value of the variable by 1
quantityval_dict[item_name]=val-1
for i in items:
#creating the quantity label, placing it between the + and - buttons and adding the label's object id to the lbldictionary
lbl=tk.Label(root,text=0)
lbl.grid(row=quantlblnum,column=2)
quantitylbl_dict[i]=[lbl]
#creating the + button , placing it before the quantity label and adding the button's object id to the plusbtndictionary
plusbtn = tk.Button(root, text="+")
plusbtn.grid(row=plusbtnnum, column=1)
plusbtn_dict[i]=[plusbtn]
#creating the - button , placing it after the quantity label and adding the button's object id to the minusbtndictionary
minusbtn = tk.Button(root, text="-")
minusbtn.grid(row=minusbtnnum, column=3)
minusbtn_dict[i]=[minusbtn]
#updating the value by one so that the buttons and labels can be placed at the next row
quantlblnum+=1
plusbtnnum+=1
minusbtnnum+=1
#assigning the plusbtnclick fucntion to each of the + buttons that we created
for plusbtnobj in plusbtn_dict:
plusbtn_dict[plusbtnobj][0].configure(command= lambda x=plusbtnobj: plusbtnclick(x))
#assigning the minusbtnclick fucntion to each of the - buttons that we created
for minusbtnobj in minusbtn_dict:
minusbtn_dict[minusbtnobj][0].configure(command=lambda x=minusbtnobj: minusbtnclick(x))
tk.mainloop()
#this loop shows the final quantities of the food that was oredered once the tkinter window is closed
for i in quantityval_dict:
print(i,' : ',quantityval_dict[i])
You can try this code out in a new file, and check whether this is what you want and take the parts you need from this code. This is a pretty long code, add me to a chatroom if you want a detailed clarification regarding anything, since using the comment section might be a hassle.
If you want to update the label, you need to store it using a variable and pass it to changeItemQuantity(). Also you need to create the label before the two buttons (- and +) so that it can be passed to the function:
# added lbl argument
def changeItemQuantity(category,item,amount,lbl):
if amount==1:
cart.items[category][item]+=1
elif amount==-1:
if cart.items[category][item]>0:
cart.items[category][item]-=1
# update label
lbl.config(text=cart.items[category][item])
...
# create the label and store the instance to a variable
lbl = tk.Label(
frm_buttons,
text=cart.items[category][itemName],
font=("Arial",16),
bg=color0,
width=2,
)
lbl.grid(
row=0,
column=1,
)
# pass "lbl" to changeItemQuantity()
tk.Button(
frm_buttons,
text="-",
font=("Arial",16),
bg=color0,
activebackground=color0,
relief=tk.FLAT,
width=2,
command=lambda itemName=itemName,lbl=lbl:changeItemQuantity(category,itemName,-1,lbl),
).grid(
row=0,
column=0,
)
# pass "lbl" to changeItemQuantity()
tk.Button(
frm_buttons,
text="+",
font=("Arial",16),
bg=color0,
activebackground=color0,
relief=tk.FLAT,
width=2,
command=lambda itemName=itemName,lbl=lbl:changeItemQuantity(category,itemName,1,lbl),
).grid(
row=0,
column=2,
)
...
I'm trying to add a label to a Tkinter button. A Tkinter button has a 'text' attribute which is great. However I need another similar text field on the button, i.e 2 text fields per button. So I would like to add a label as a child to the button. The problem with this is that it creates unpredictable behaviour. Actuating the button or even just hovering the mouse over the label causes the label to vanish.
button_1 = tk.Button(canvas, text='Take',command='take',width='12')
button_1.pack()
label_1 = tk.Label(button_1, text='Stream 1',font=('calibre',8))
label_1.pack()
label_1.place(x=10, y=10)
I can avoid this unwanted behaviour by adding the label to the same parent as the button.
label_1 = tk.Label(canvas, text='Stream 1',font=('calibre',8))
But by doing this I loose the convenience of positioning the label with the button's geometry, e.g inset by 10 pixels from left and 10 pixels from top of button. I plan to make a convenience method for adding hundreds of such buttons, and do not wish to be attempting to calculate each label in the parents coordinates. And besides, as a sub-child of the button, it becomes part of the button hierarchy, i.e. will move with the button, will be hidden with the button etc.
You can display multiple lines of text on a Button by embedding newlines in the text.
Like this:
import tkinter as tk
root = tk.Tk()
root.geometry('100x100')
canvas = tk.Canvas(root)
canvas.pack()
button_1 = tk.Button(canvas, text='Take\nStream 1',command='take',width='12')
button_1.pack()
#label_1 = tk.Label(button_1, text='Stream 1',font=('calibre',8))
#label_1.pack()
#label_1.place(x=10, y=10)
root.mainloop()
Result:
You can use the anchor and justify options to change the positioning of the text on the Label:
button_1 = tk.Button(canvas, text='Take\nStream 1', command='take', width='12',
anchor='w', justify='left')
button_1.pack()
Result 2:
So, I have 4 Entry Widgets on my window and I just wanted to add some internal left padding on the last Entry Widget. I did so using ttk.style(), which added the desired padding but it also added some additional styling like a black border, some hover effect, then the entry widget gets a blue border when selected.
This is my Code:
from tkinter import *
from tkinter import ttk
root = Tk()
root.configure(padx=50)
input1 = Entry(root)
input1.grid(row=1, column=0, pady=10)
input2 = Entry(root)
input2.grid(row=2, column=0, pady=10)
input3 = Entry(root)
input3.grid(row=3, column=0, pady=10)
style = ttk.Style(root)
style.configure('padded.TEntry', padding=[15, 0, 0, 0])
e = ttk.Entry(root, style='padded.TEntry')
e.grid(row=4,column=0, pady=10)
root.mainloop()
Look how the 4th Entry Widget has a Black Border around it
Look how a Blue Border appears when the widget is selected
The only styling that I was excepting is the little increase in width because of the left-padding, but how are these other styles being triggered.
It is because the fourth entry is a ttk.Entry widget but the other three are tkinter.Entry widgets. If you make all four ttk.Entry widgets you'll see that they all have the additional styles.
Even though the tkinter and ttk modules have widgets with the same name, they are completely different widgets with their own sets of defaults.
I have been doing some work in python and have came across Tkinter which is very useful for my project. I was in the process of making a screen where there is a button and a text entry box however when the text entry box is present, no button shows up. However when I remove the entry box, I can see the button.
Here is the part of the script I've been working on:
Hi, I have been doing some work in python and have came across Tkinter which is very useful for my project. I was in the process of making a screen where there is a button and a text entry box however when the text entry box is present, no button shows up. However when I remove the entry box, I can see the button.
from tkinter import *
def submit1():
print("working");
def password1():
passwordbox= Tk()
passwordbox.title("Password Verification")
passwordbox.configure(background="white")
passwordbox.geometry("1000x1000")
box1 = Entry(passwordbox, width=200, bg="gray")
box1.grid(row=2000, column=10, sticky=W)
submit = Button(passwordbox, text="Submit", width=20, height=5,
bg="black", fg="white", command=submit1)
submit.grid(row=1000, column=15, sticky=W);
password1()
The text box should show to entry box and the button however it only shows the button
If the entry box code was # out, the button will work
Anyone got any ideas?
You need to add a line passwordbox.mainloop() at the end of password1 definition. Also you need to specify row and column of the grid properly.
Set Entry box to row 0 and column 0
Set Submit button to row 1 and column 0
from tkinter import *
def submit1():
print("working");
def password1():
passwordbox= Tk()
passwordbox.title("Password Verification")
passwordbox.configure(background="white")
passwordbox.geometry("1000x1000")
box1 = Entry(passwordbox, width=200, bg="gray")
box1.grid(row=0, column=0, sticky=W)
submit = Button(passwordbox, text="Submit", width=20, height=5,
bg="black", fg="white", command=submit1)
submit.grid(row=1, column=0, sticky=W);
passwordbox.mainloop()
password1()
I am doing a GUI using for my code and finding it hard to develop what i want.
First ...
on the window an image and label appear and disappear after couple of seconds. after that a frame should arrive, followed by a menu option, when the menu option is selected it should disappear from frame and another image should appear.
self.gui = Tk()
def configure_window(self):
self.gui.geometry('700x500')
self.gui.resizable(height=False, width=False)
self.gui.update()
self.welcome_label()
self.welcome_image()
def welcome__image(self):
image = Image.open(image path)
photo = ImageTk.PhotoImage(image)
welcome_image = Label(master=self.gui, image=photo, justify='center',relief='raised')
welcome_image.image = photo
welcome_image.pack(padx=100, pady=100)
welcome_image.place(bordermode='outside', x=200,y=100)
welcome_image.after(ms=1000, func=welcome_image.place_forget)
def welcome_label(self):
welcome_label = Label(master=self.gui, text=welcome_label_text,font=welcome_label_font, justify='center',
state='active', fg='blue', anchor='center', pady=10, relief='raised' )
welcome_label.place(x=100, y=350)
welcome_label.after(ms=1000, func=welcome_label.place_forget)
def first_frame(self):
self.first_frame = LabelFrame( master = self.gui,text='Select First File',font= 'arial 10 bold underline', bg=self.background_colour,
height=200,width=690, bd=1, padx=5, pady=5)
self.first_frame.pack()
self.first_frame.place(x=0, y=0)
def select_button1(self):
self.file_open_button1 = Button(master=self.first_frame,text='...',state='disabled',command=self.cmd_file_select_button1)
when i run the code, welcome_image and welcome_frame places and forgets places after 1000 ms, but, first_frame also loads with parent widget. i want frame to arrive after the welcome_label disappears.
and the same sequence continues for LabelFrame
i have widgets in LabelFrame in which i have OptionMenu and button, i want optionMenu to disappear when user selects an option and another button should appear, same sequence in the button, button returns some sting and a label should appear in the place of button etc.
how to generate the sequence e.g. if an widget has place_forget() a sequence will be generated and another widget should be bind on that sequence. please help.
apology, if i am unable to make you understand my problem.