How to edit widget label's text which is generated in a for loop?

Question:

I’m trying to make a shopping basket/cart which contains items from a dictionary. Dictionary holds item name and its price. The code below displays items from dictionary and multiply quantity to price. There are + and - buttons which should modify the quantity for specific item, but something is wrong with my code and buttons are changing only last item’s quantity and won’t change the price according to quantity. I hope you can help me. Thanks.

from tkinter import *

def add_qty():
    qty = qtity_label.cget('text')
    qty += 1
    qtity_label.config(text=qty)
    print(qty)


def sub_qty():
    qty = qtity_label.cget('text')
    qty -= 1
    qtity_label.config(text=qty)
    print(qty)

root = Tk()
shopping_basket = {'nerf': 25.00,
                   'lego': 10.00,
                   'ball': 5.00}
row = 0
for item in shopping_basket:
    item_name_lbl = Label(root, text=item)
    item_name_lbl.grid(column=0, row=row)
    qtity_label = Label(root, text=2)
    qtity_label.grid(column=5, row=row)
    price_lbl = Label(root, text=qtity_label.cget('text')*shopping_basket[item])
    price_lbl.grid(column=9, row=row)
    sub_qtity_btn = Button(root, text='-', command=sub_qty)
    sub_qtity_btn.grid(column=4, row=row)
    add_qtity_btn = Button(root, text='+', command=add_qty)
    add_qtity_btn.grid(column=6, row=row)
    row += 1

root.mainloop()
Asked By: Venato1488

||

Answers:

You’re overwriting qtity_label in the loop, so it will be whatever the last thing you set it to was. By the time add_qty runs, it only sees the last value of qtity_label, so that’s what it updates.

You need to save all the labels separately, or pass them into the command functions, and use the passed in label instead of the global label:

def add_qty(label):  # Note how the label is being passed-in
    qty = label.cget('text')  # And then the passed-in label is used
    qty += 1
    label.config(text=qty)
    print(qty)


def sub_qty(label):
    qty = label.cget('text')
    qty -= 1
    label.config(text=qty)
    print(qty)

Then:

sub_qtity_btn = Button(root, text='-', command=lambda q=qtity_label: sub_qty(q))
. . .
add_qtity_btn = Button(root, text='+', command=lambda q=qtity_label: add_qty(q))

For an explanation of why you need q=qtity_label, see this post. Basically, q is qtity_label; but is being saved in a way that can’t be accidentally overwritten later (kind of).

Answered By: Carcigenicate

Since you used same set of names in the for loop, therefore after the for loop the set of names point to the last assignments.

One of the ways is to pass the current references of the labels to the two functions in each loop by using functools.partial():

from functools import partial

def add_qty(item, qtity_label, price_lbl):
    qty = qtity_label.cget('text')
    qty += 1
    qtity_label.config(text=qty)
    price_lbl.config(text=shopping_basket[item]*qty)
    print(qty)


def sub_qty(item, qtity_label, price_lbl):
    qty = qtity_label.cget('text')
    qty -= 1
    qtity_label.config(text=qty)
    price_lbl.config(text=shopping_basket[item]*qty)
    print(qty)

...

for item in shopping_basket:
    item_name_lbl = Label(root, text=item)
    item_name_lbl.grid(column=0, row=row)
    qtity_label = Label(root, text=2)
    qtity_label.grid(column=5, row=row)
    price_lbl = Label(root, text=qtity_label.cget('text')*shopping_basket[item])
    price_lbl.grid(column=9, row=row)
    sub_qtity_btn = Button(root, text='-', command=partial(sub_qty, item, qtity_label, price_lbl))
    sub_qtity_btn.grid(column=4, row=row)
    add_qtity_btn = Button(root, text='+', command=partial(add_qty, item, qtity_label, price_lbl))
    add_qtity_btn.grid(column=6, row=row)
    row += 1
Answered By: acw1668
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.