Home > other >  How do I (efficiently) update the text of a tkinter label that was procedurally generated?
How do I (efficiently) update the text of a tkinter label that was procedurally generated?

Time:05-09

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

CodePudding user response:

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.

CodePudding user response:

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,
)
...
  • Related