-1

I'm trying to create lambda function with dynamic argument function in dictionary, then assign it into each Checkbutton command argument. When I tick every checkbutton, the entrie Entrys are still greyed out except the last Entry can enable and disable via checkbutton

First, I'm assign checkbox command argument with lambda function with dynamic argument from another ditcionary into function directly, Then trying to store as separate command dictionary first and assign with specific key from this dictionary into the command argument. The results are still the same

I'm try to debug by getting each variable from the dictionary, every Checkbutton are linked and work correctly, but I'm curious why command isn't working except the last Entry. I'm noted that everytime I enable and disable the whole Checkbutton. I assume that the command argument from every Checkbuttons are linked with the last variable from the dictionaries.

But when I print the argument in function qCheckboxAction(checkboxVar,entryObj,entryVar) then I found out that entire items of each groups is replaced by the argument of the last item from the groups.

How can I assign each of command Checkbuttons correctly to prevent from previous loop is changed by key from current loop.

PS. I have solved the problem because lambda must scope the enclosure.

My JSON Data CafeData.json

{
    "Coffee":
    {
        "Lattae":30,
        "Espresso":35,
        "Ice Lattae":40,
        "Vale Coffee":45,
        "Cappuccino":35,
        "African Coffee":45,
        "American Coffee":45,
        "Ice Cappuccino":40
    },
    "Cake":
    {
        "Coffee Cake":40,
        "Red Velvet Cake":45,
        "Black Forest Cake":50,
        "Boston Cream Cake":50,
        "Lagos Chocolate":45,
        "Kilburn Chocolate Cake":50,
        "Carton Hill Cake":40,
        "Queen Park Cake":50
    },
    "Service Charge":
    {
        "Type":"Flat",
        "Value":1.59
    },
    "Paid Tax":
    {
        "Type":"Percent",
        "Value":0.15
    }
}

My Python Code

from tkinter import *
import json
import random
import time
from tkinter import messagebox

cafeData = {}

try:
    with open("CafeData.json", encoding="utf-8") as f:
        cafeData = json.loads(f.read())
except FileNotFoundError:
    print("File not found. Please check CafeData.json file is in the same directory.")
    exit()

# String variable
appTitle = "Engineering Sriracha Coffee Shop"

# Font Profiles
labelFontProfile = ("TH Sarabun New", 60, "bold")
cafeInfoFontProfile = ("TH Sarabun New", 18, "bold")
normalReceiptFontProfile = ("TH Sarabun New",12,"bold")
smallReceiptFontProfile = ("TH Sarabun New",11,"bold")

# Coffee Variable
coffeePriceDict = cafeData["Coffee"]

# Cake Variable
cakePriceDict = cafeData["Cake"]

# Cost Item Information
itemInfoNameList = ["Cost of Drinks","Cost of Cakes","Service Charge"]
costGUIData = {}

# Payment Information
paymentInfoName = ["Paid Tax","Sub Total", "Total"]
paymentGUIData = {}

# Initialize GUI
app = Tk()
app.geometry("1200x900+0+0")
app.title(appTitle)
app.configure(background="cyan")

# Frame Properties
topFrame = Frame(app, width=1350, height=100, bd=14, relief="raise")
topFrame.pack(side=TOP)
mainLeftFrame = Frame(app, width=900, height=650, bd=8, relief="raise")
mainLeftFrame.pack(side=LEFT)
mainRightFrame = Frame(app, width=440, height=650, bd=8, relief="raise")
mainRightFrame.pack(side=RIGHT)
mainLeftTopFrame = Frame(mainLeftFrame, width=900, height=320, bd=8, relief="raise")
mainLeftTopFrame.pack(side=TOP)
mainLeftBottomFrame = Frame(mainLeftFrame, width=900, height=320, bd=8, relief="raise")
mainLeftBottomFrame.pack(side=BOTTOM)
receiptFrame = Frame(mainRightFrame, width=440, height=450, bd=12, relief="raise")
receiptFrame.pack(side=TOP)
buttonFrame = Frame(mainRightFrame, width=440, height=240, bd=12, relief="raise")
buttonFrame.pack(side=BOTTOM)
coffeeFrame = Frame(mainLeftTopFrame, width=400, height=330, bd=16, relief="raise")
coffeeFrame.pack(side=LEFT)
cakeFrame = Frame(mainLeftTopFrame, width=400, height=320, bd=16, relief="raise")
cakeFrame.pack(side=RIGHT)
cost1Frame = Frame(mainLeftBottomFrame, width=440, height=320, bd=14, relief="raise")
cost1Frame.pack(side=LEFT)
cost2Frame = Frame(mainLeftBottomFrame, width=440, height=320, bd=14, relief="raise")
cost2Frame.pack(side=RIGHT)

topFrame.configure(background="pink")
mainLeftFrame.configure(background="blue")
mainRightFrame.configure(background="blue")

# Function
def createCafeGUI(namePriceDict,parentFrame,fontProfile):
    index=0
    guiDataDict = {}
    for key in namePriceDict.keys():
        guiDataDict[key] = {} # Need to tell python an assignment for using nested dictionary
        guiDataDict[key]["Price"] = namePriceDict[key]
        guiDataDict[key]["CheckboxVariable"] = IntVar()
        guiDataDict[key]["CheckboxVariable"].set(0)
        guiDataDict[key]["EntryVariable"] = StringVar()
        guiDataDict[key]["EntryVariable"].set(0)

        guiDataDict[key]["EntryObject"] = Entry(parentFrame, font=fontProfile,bd=8,width=6,justify="left",textvariable=guiDataDict[key]["EntryVariable"],state=DISABLED)
        guiDataDict[key]["EntryObject"].grid(row=index,column=1)
        guiDataDict[key]["CheckboxCommand"] = lambda k=key:qCheckboxAction(guiDataDict[k]["CheckboxVariable"],guiDataDict[k]["EntryObject"],guiDataDict[k]["EntryVariable"])
        guiDataDict[key]["CheckboxObject"] = Checkbutton(parentFrame,text=key+"\t",variable=guiDataDict[key]["CheckboxVariable"],onvalue=1,offvalue=0,font=fontProfile,command=guiDataDict[key]["CheckboxCommand"])
        guiDataDict[key]["CheckboxObject"].grid(row=index,sticky=W)
        index+=1

    return guiDataDict

def createPaymentInfo(nameList,parentFrame,fontProfile):
    index=0
    guiData = {}
    for name in nameList:
        guiData[name] = {}
        guiData[name]["EntryVariable"] = StringVar()
        guiData[name]["EntryVariable"].set("฿ 0.00")
        guiData[name]["LabelObject"] = Label(parentFrame,font=fontProfile,text=name,bd=8)
        guiData[name]["LabelObject"].grid(row=index,column=0,sticky=W)
        guiData[name]["EntryObject"] = Entry(parentFrame,font=fontProfile,bd=8,justify="left",textvariable=guiData[name]["EntryVariable"])
        guiData[name]["EntryObject"].grid(row=index,column=1,sticky=W)
        index+=1
    return guiData

def qCheckboxAction(checkboxVar,entryObj,entryVar):
    if checkboxVar.get() == 1:
        entryObj.configure(state=NORMAL)
    elif checkboxVar.get() == 0:
        entryObj.configure(state=DISABLED)
        entryVar.set(0)

def qExit():
    confirmed = messagebox.askyesno("Quit System!!!", "Do you want to quit?")
    if confirmed > 0:
         app.destroy()
         return

def qReset():
    qGroupReset(coffeeGUIData)
    qGroupReset(cakeGUIData)
    qCostReset()
    textReceipt.delete("1.0",END)

def qGroupReset(guiDataDict):
    for key in guiDataDict.keys():
        guiDataDict[key]["CheckboxVariable"].set(0)
        guiDataDict[key]["EntryVariable"].set(0)
        guiDataDict[key]["EntryObject"].configure(state=DISABLED)

def qCostReset():
    for key in costGUIData.keys():
        costGUIData[key]["EntryVariable"].set("฿ 0.00")
    for key in paymentGUIData.keys():
        paymentGUIData[key]["EntryVariable"].set("฿ 0.00")

def costOfGroup(guiDataDict):
    total = 0
    for name in guiDataDict.keys():
        total += guiDataDict[name]["Price"] * int(guiDataDict[name]["EntryVariable"].get())
    return total

def qCostOfItem():
    try:
        totalCoffeePrice = costOfGroup(coffeeGUIData)
        totalCakePrice = costOfGroup(cakeGUIData)
        total = totalCakePrice + totalCoffeePrice
        isHaveOrdered = total > 0
        costFormatDict = {"Cost of Drinks":"฿ {:.2f}".format(totalCoffeePrice),
                          "Cost of Cakes":"฿ {:.2f}".format(totalCakePrice * isHaveOrdered),
                          "Service Charge":"฿ {:.2f}".format(addModifier(total,cafeData["Service Charge"]) * isHaveOrdered)}
        paymentFormatDict = {"Paid Tax":"฿ {:.2f}".format(addModifier(total + addModifier(total,cafeData["Service Charge"]),cafeData["Paid Tax"]) * isHaveOrdered),
                             "Sub Total":"฿ {:.2f}".format((total + addModifier(total,cafeData["Service Charge"])) * isHaveOrdered),
                             "Total":"฿ {:.2f}".format((total + addModifier(total,cafeData["Service Charge"])  + addModifier(total + addModifier(total,cafeData["Service Charge"]),cafeData["Paid Tax"])) * isHaveOrdered)}
        for key in costFormatDict.keys():
            costGUIData[key]["EntryVariable"].set(costFormatDict[key])
        for key in paymentFormatDict.keys():
            paymentGUIData[key]["EntryVariable"].set(paymentFormatDict[key])
        return total
    except ValueError:
        qReset()
        return 0

def qPrintGroupReceipt(groupGUIData):
    for name in groupGUIData.keys():
        if int(groupGUIData[name]["EntryVariable"].get()) > 0:
            textReceipt.insert(END,name+"\t\t\t\t\t"+str(int(groupGUIData[name]["EntryVariable"].get()))+"\n")

def qPrintCostReceipt(costGUIData):
    for name in costGUIData.keys():
        textReceipt.insert(END,name+" : \t\t"+costGUIData[name]["EntryVariable"].get()+"\n")

def qReceipt():
    textReceipt.delete("1.0",END)
    showReceipt = qCostOfItem() > 0
    if not showReceipt: return # Guard clause
    billNumber = random.randint(10908,500876)
    dateOfOrder = time.strftime("%d/%m/%Y")
    textReceipt.insert(END,"Receipt Ref:\t\t\tBILL" + str(billNumber) + "\t\t" + dateOfOrder + "\n")
    textReceipt.insert(END,"Item\t\t\t\t\t" + "Cost of Items\n\n")
    qPrintGroupReceipt(coffeeGUIData)
    qPrintGroupReceipt(cakeGUIData)
    textReceipt.insert(END,"\n")
    qPrintCostReceipt(costGUIData)

def addModifier(value,property):
    if property["Type"] == "Flat":
        return property["Value"]
    elif property["Type"] == "Percent":
        return value * property["Value"]
    return 0

# Create GUI
coffeeGUIData = createCafeGUI(coffeePriceDict,coffeeFrame,cafeInfoFontProfile)
cakeGUIData = createCafeGUI(cakePriceDict,cakeFrame,cafeInfoFontProfile)

# Create Payment Informent
costGUIData = createPaymentInfo(["Cost of Drinks","Cost of Cakes","Service Charge"],cost1Frame,cafeInfoFontProfile)
paymentGUIData = createPaymentInfo(["Paid Tax","Sub Total","Total"],cost2Frame,cafeInfoFontProfile)

# Label Properties
labelInfo = Label(topFrame, font=labelFontProfile, text=appTitle, bg="yellow", bd=18)
labelInfo.grid(row=0, column=0)

# Receipt Information
labelReceipt = Label(receiptFrame, font=normalReceiptFontProfile, text="Receipt",bd=2,anchor="w")
labelReceipt.grid(row=0,column=0,sticky=W)
textReceipt = Text(receiptFrame,font=smallReceiptFontProfile,bd=8,width=59,height=22,bg="white")
textReceipt.grid(row=1,column=0)

# Button Control Properties
btnCommandDict = {"Total":lambda:qCostOfItem(),"Receipt":lambda:qReceipt(),"Reset":lambda:qReset(),"Exit":lambda:qExit()}
index = 0
for btnName in btnCommandDict.keys():
    btnRef = Button(buttonFrame,padx=16,fg="black",font=cafeInfoFontProfile,width=5,text=btnName,command=btnCommandDict[btnName]).grid(row=0,column=index)
    index+=1

app.mainloop()

1 Answers1

0

Change:

lambda:qCheckboxAction(coffeeRefCheckbox[coffee],coffeeRefEntryObject[coffee],coffeeRefEntry[coffee])

to:

lambda c=coffee:qCheckboxAction(coffeeRefCheckbox[c],coffeeRefEntryObject[c],coffeeRefEntry[c])

Also, change:

lambda:qCheckboxAction(cakeRefCheckbox[cake],cakeRefEntryObject[cake],cakeRefEntry[cake])

to:

lambda c=cake:qCheckboxAction(cakeRefCheckbox[c],cakeRefEntryObject[c],cakeRefEntry[c])

Look into Python closures, and how they relate to lambdas.

Paul M.
  • 10,481
  • 2
  • 9
  • 15