我如何(有效地)更新程序生成的 tkinter 标签的文本?

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

我正在制作一个餐厅菜单程序,它从 .json 文件中提取项目及其属性,然后允许用户将它们添加到购物车并导出订单(不用于实际使用,只是一个项目)

我为这个项目设定的目标是通过简单地更改 .json 文件中的项目来自定义菜单,然后程序自动显示它们(我希望它尽可能模块化)

我已经解决了按钮事件和创建按钮等问题,但购物车按钮有问题。每个项目旁边都有“+”和“-”按钮,标签显示所选数量。 Here is an image

我需要在按下 +/- 按钮时更改中间的标签,但由于它是使用 for 循环创建的,所以我不知道该怎么做。我的一个想法是每次按下按钮时重新打印整个屏幕,这对于这么简单的事情应该可以正常工作,但我认为这将是一个坏主意,因为对于更大的事情来说这不是一个可接受的解决方案。我想知道是否有更好的方法,以及如何实现它。

下面是我的一段代码(删除了所有花哨的颜色和图像),它看起来不太好,但它可以很好地重现问题。我还将 menu.json 中的几行复制到代码中,以便它可以 运行 自己。

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

我建议您不要创建新标签来在单击 + 或 - 时更改值,而是更新同一标签所持有的值。
示例代码:

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])

您可以在新文件中尝试这段代码,检查这是否是您想要的,并从这段代码中提取您需要的部分。这是一个很长的代码,如果您想详细说明任何事情,请将我添加到聊天室,因为使用评论部分可能会很麻烦。

如果你想更新标签,你需要使用变量存储它并传递给changeItemQuantity()。您还需要在两个按钮(-+)之前创建标签,以便将其传递给函数:

# 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,
)
...