-1

I am trying to create a GUI in Python for a class assignment called 'Chocolate Vending Machine'. Students are required to use Python's Tkinter. We list a couple of chocolate brands such as Snickers, Twix and Mars and put a price tag on each of them. The user goes through these steps:

  1. Input a float value of how much money he has (can be any value)
  2. He selects how many of each chocolate he wants
  3. That value is printed to the console under: "Total Cost"
  4. The original balance (how much money he originally had) minus Total Cost prints the new balance

Please refer to the code below. How can I abstract the following lines of code so that it still works, but limits the number of lines? Note that I am not pasting the whole code of the application as it is just too many lines of code. So here's a snippet to narrow things down:

#----------------------Chocolate---------------------

Twix=DoubleVar()
Snickers=DoubleVar()
MarsBar=DoubleVar()
Godiva=DoubleVar()

lblTwix = Label(f1, font=('arial', 16, 'bold'), text='Twix',bg = 'powder blue',bd=19,anchor='w')
lblTwix.grid(row=2,column=0)
txtTwix = Entry(f1,font=('arial',16,'bold'),textvariable=Twix,bd=10,insertwidth=4,
                bg='powder blue',justify='right')
txtTwix.grid(row=2,column=1)

lblSnickers = Label(f1, font=('arial', 16, 'bold'), text='Snickers', bg = 'powder blue',bd=19,anchor='w')
lblSnickers.grid(row=4,column=0)
txtSnickers = Entry(f1,font=('arial',16,'bold'),textvariable=Snickers,bd=10,insertwidth=4,
                bg='powder blue',justify='right')
txtSnickers.grid(row=4,column=1)

lblMarsBar = Label(f1, font=('arial', 16, 'bold'), text='Mars Bar', bg = 'powder blue',bd=19,anchor='w')
lblMarsBar.grid(row=6,column=0)
txtMarsBar = Entry(f1,font=('arial',16,'bold'),textvariable=MarsBar,bd=10,insertwidth=4,
                bg='powder blue',justify='right')
txtMarsBar.grid(row=6,column=1)

lblGodiva = Label(f1,font=('arial',16,'bold'), text="Godiva", bg = 'powder blue',bd=19, anchor='w')
lblGodiva.grid(row=9, column=0)
txtGodiva = Entry(f1,font=('arial',16,'bold'),textvariable=Godiva,bd=10,insertwidth=4,
                bg='powder blue',justify='right')
txtGodiva.grid(row=9,column=1)

#==================================Balance, Cost=======================================

InitialBalance=DoubleVar()
Cost=StringVar()
FinalBalance=StringVar()

lblInitialBalance = Label(f1,font=('arial',20,'bold'), text="Initial Balance", bg = 'powder blue', bd=29, anchor='w')
lblInitialBalance.grid(row=2, column=10)
txtInitialBalance=Entry(f1,font=('arial',20,'bold'), textvariable=InitialBalance, bd=28, insertwidth=4,
                   bg='steel blue', justify='right')
txtInitialBalance.grid(row=2, column=12)

lblCost = Label(f1,font=('arial',20,'bold'), text="Total Cost", bg = 'powder blue',bd=29, anchor='w')
lblCost.grid(row=4, column=10)
txtCost=Entry(f1,font=('arial',20,'bold'), textvariable=Cost, bd=28, insertwidth=4,
                   bg='powder blue', justify='right')
txtCost.grid(row=4, column=12)

lblFinalBalance = Label(f1,font=('arial',20,'bold'), text="Final Balance", bg = 'powder blue',bd=29, anchor='w')
lblFinalBalance.grid(row=6, column=10)
txtFinalBalance=Entry(f1,font=('arial',20,'bold'), textvariable=FinalBalance, bd=28, insertwidth=4,
                   bg='powder blue', justify='right')
txtFinalBalance.grid(row=6, column=12)

REVISED CODE:

Twix=DoubleVar()
Snickers=DoubleVar()
MarsBar=DoubleVar()
Godiva=DoubleVar()

def make_entry(frame, text, variable, row, col1, col2):
    label = Label(frame, font=('arial',20,'bold'), text=text, textvariable = variable, bg = 'powder blue', bd=19, anchor='w')
    label.grid(row=row, column=col1)
    entry=Entry(f1,font=('arial',20,'bold'), textvariable=variable, bd=28, insertwidth=4,
                       bg='steel blue', justify='right')
    entry.grid(row=row, column=col2)
    return label, entry

    make_entry(f1, "Twix", Twix, 2, 0, 1)
    make_entry(f1, "Snickers", Snickers, 4, 0, 1)
    make_entry(f1, "MarsBar", MarsBar, 6, 0, 1)
    make_entry(f1, "Godiva", Godiva, 9, 0, 1)

And here is a link to the full, non-abstracted code:

https://docs.google.com/document/d/1tMJxmFAMWyc6qhTFnhoedQmj49Qkf56rJxhxB8Z3fSM/edit?usp=sharing

As you can see, every line of code pasted above has to do with the styling of the buttons and the boxes that the user can input necessary information for the program to do its job. With all of that said, I reiterate the question:

what is the most efficient way to condense all of this code into fewer lines and still make it work? How could I use classes and def statements to rewrite the code in a more efficient fashion?

I have attempted to abstract the code above, but it did not work. Any help is appreciated.

1 Answers1

1

You can define a function for creating the Label and Entry with the given parameters:

def make_entry(frame, text, variable, row, col1, col2):
    font = ('arial',16,'bold')
    label = Label(frame, font=font, text=text, bg='powder blue', bd=19, anchor='w')
    label.grid(row=row, column=col1)
    entry = Entry(frame, font=font, textvariable=variable, bg='powder blue', bd=10, insertwidth=4, justify='right')
    entry.grid(row=row, column=col2)

Then, just call that function for your different candies.

make_entry(f1, "Twix", Twix, 2, 0, 1)
make_entry(f1, "Snickers", Snickers, 4, 0, 1)
...

You might also have the function return label, entry if you need those later on, but it seems like the variables are enough.

The parameters in the second group are a bit different: Larger font size, thicker borders, different shades of blue. You could either make those additional parameters to the function, or create a second function for those buttons similar to the first.

Alternatively, you could also just create two loops instead of defining a function. Particularly if the differences between the first and the second group of entries are more significant this may be simpler than using two very similar functions, or one function with very many parameters.

candies = [("Twix", Twix), ("Snickers", Snickers),
           ("Mars Bar", MarsBar), ("Godiva", Godiva)]
font = ('arial',16,'bold')
for i, (name, var) in enumerate(candies, start=1):
    label = Label(f1, font=font, text=name, bg='powder blue', bd=19, anchor='w')
    label.grid(row=2*i, column=0)
    entry = Entry(f1, font=font, textvariable=var, bg='powder blue', bd=10, insertwidth=4, justify='right')
    entry.grid(row=2*i, column=1)

Also, unrelated to the actual problem, DoubleVar does not seem to be a sensible choice for a countable quantity like chocolate bars. Are your users supposed to be able to buy 3.14 Mars bars? Instead, I'd suggest using IntVar.


About your "revised code": There are two problems: First, the use of the function make_entry(f1, ...) should not be inside the function, but on the root level (no indentation). Second, you accidentally added textvariable=variable to the Label constructor, where it does not belong; only to Entry.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • I see the logic behind your code, but for some reason, it doesn't work. When I run the program, it does not seem to print the candy name nor the box where the user enters the quantity. – user8244818 Feb 28 '18 at 12:36
  • @user8244818 Just noticed, that the font size and `bd` attributes are also different for the two types of buttons, so you might add parameters to the function for those, too. Apart from that, the labels and entries appeared just fine when I tried. – tobias_k Feb 28 '18 at 13:04
  • Hmm, that's strange. I don't understand why mine doesn't work. Did you copy and paste the full code? I'm still really stuck. – user8244818 Feb 28 '18 at 13:52
  • @user8244818 I copied the entire code form the link, replaced two of the four chocolate entries with the function, and they looked the same and worked. Are you sure that your code has not diverged from what you posted in the meantime? What exactly do you see? Do you get an error, or are the widgets simply missing from the UI? – tobias_k Feb 28 '18 at 14:13
  • Before I go any further, I'd like to thank you so much for trying to help me out, even if I'm still experiencing difficulties. – user8244818 Feb 28 '18 at 14:43
  • I wish I could send you a screenshot of the program, but right now with your function the GUI is empty. Nothing is showing up :( – user8244818 Feb 28 '18 at 14:43
  • @user8244818 Can you update your question with your new code? Maybe there's just a misunderstanding how to use the function. (Please do not replace the existing code, but add the updated code below.) – tobias_k Feb 28 '18 at 14:52
  • @user8244818 The problem is clear: The calls to `make_entry` should _not_ be _inside_ the function. but on the top-level, same level of indentation as your current labels and entries are. – tobias_k Feb 28 '18 at 15:50
  • I put it outside of the function, but still below it. Is that okay? It seems to work now, except the "text" parameter doesn't work; it won't print the name of the chocolate on the left hand side. Any ideas? – user8244818 Mar 01 '18 at 12:08
  • @user8244818 The labels are gone because you use `text=text, textvariable=variable` This sets the text to `text` and then immediately replaces it with the value of the `variable`, i.e. the number of items. For `label`, you need _only_ `text=text`, and for `entry`, _only_ `textvariable=variable` – tobias_k Mar 01 '18 at 12:21
  • Oh my goodness. Thank you so much. Is there any way we could communicate in the future directly for help? Please let me know. You are a lifesaver. – user8244818 Mar 01 '18 at 12:36
  • @user8244818 Nope, when you have another question, just ask it, and if not myself, someone else will answer. Also, if this solved your problem (and it seems like it did) please consider _accepting_ the answer, to mark the question as being resolved. – tobias_k Mar 01 '18 at 12:42
  • Hold up, there's another problem, sorry. Abstracting the code as we've done here removes the variables txtCost and txtFinalBalance, which do the job of actually performing the functions (adding up the cost for all the chocolates, and subtracting that sum from the initial balance, respectively). Search up txtCost.insert(0, CostOfMeal) and txtFinalBalance.insert(0, FinalBal) on the document and you'll see what I mean. On the unobstructed code, I basically used those variables to print the Label and Entry boxes. How can I maintain the abstracted code while fixing this issue? – user8244818 Mar 01 '18 at 13:04
  • @user8244818 You should not use those _at all_. That's what the `textvariables` are for. Instead of `txtFinalBalance.insert(0, FinalBal)`, use `FinalBalance.set(FinalBal)`, like you do in the `reset` function. And if for some reason you _do_ need the `entry` itself, you can still `return` it from the function and bind it to some global variable. – tobias_k Mar 01 '18 at 13:15