Connecting button to run function to add labels - python

I just finished reading a Python book called Practical Programming 2nd ed. and thought I'd challenge myself to apply what I learned to make my first small program for work (convenience store) to create a list of chips I need to order using tkinter as the interface.
I would input how many of each brand of chips I have in stock, and after pressing the 'Order' button, it would return the number of each brand of chips needed to order.
Here is the code I've got so far:
import tkinter as tk
# Initiate tkinter
window = tk.Tk()
# Create a dictionary of brand of chips with a value of how many chips should be in stock
chips = {'Miss Vickies Original': 4, 'Miss Vikies Salt & Vinegar': 4}
# Start counting rows
row_num = 0
for brand in chips:
# Create label describing the brand of chips
label = tk.Label(window, text=brand)
label.grid(row=row_num, column=1)
# Create an entry box to input number of chips in stock
entry = tk.Entry(window)
entry.grid(row=row_num, column=2)
# Add one; To start at next row
row_num += 1
# Return number of chips to order
def order(entry_data, stock_required):
# Subtract how much is in stock from how much should be in stock
order_quantity = int(stock_required) - int(entry_data)
# Create label to the left of each chip with how many should be ordered
order_label = tk.Label(window, text="'Order: ', order_quantity")
order_label.pack(side='left')
# Order button
button = tk.Button(window, text='Order', command=lambda: order(entry.get(), chips[1]))
button.pack(side='bottom')
window.mainloop()
I think I've confused myself with my own code lol. I'm having great difficulty figuring out how to make the button(placed at the bottom) trigger the calculation to occur and show the order_label to the left of each chips label.
A problem I see with my code that I'm not sure how to fix is the label would only be called once, as the function doesn't contain a loop. What's the recommended way to solve this?
I've checked: Tkinter: Link an Entry Widget to a Button to a Function.
But not a related problem, as I have the lambda: order function already in.
Here is how it should look like:
This is my first program, so any constructive criticism is encouraged!

I added the functionality needed to make your code work as you intended; You will need to keep track of the current level of stock to make this something useful though.
import tkinter as tk
def generate_order_quantities():
for brand, datafields in chips_entry_fields.items():
entry, quantity_to_order, label = datafields
recommended_stock_level = chips_stock_levels[brand]
try:
quantity_sold = int(entry.get())
except ValueError:
quantity_sold = 0
quantity_to_order.set(min(quantity_sold, recommended_stock_level))
if __name__ == '__main__':
# Initiate tkinter
window = tk.Tk()
# Create a dictionary of brand of chips_stock_levels with a value of how many chips_stock_levels should be in stock
chips_stock_levels = {'Miss Vickies Original': 4, 'Miss Vikies Salt & Vinegar': 4}
chips_quantities_sold = {'Miss Vickies Original': 0, 'Miss Vikies Salt & Vinegar': 0}
chips_entry_fields = {}
# Start counting rows
row_num = 0
for row, brand in enumerate(chips_stock_levels):
# Create label describing the brand of chips_stock_levels
label = tk.Label(window, text=brand)
label.grid(row=row, column=1)
# Create an entry box to input number of chips_stock_levels in stock
entry = tk.Entry(window)
chips_entry_fields[brand] = [entry]
entry.grid(row=row, column=2)
# Create label to the left of each chip with how many should be ordered
quantity_to_order = tk.IntVar()
chips_entry_fields[brand].append(quantity_to_order)
# quantity_to_order.set()
order_label = tk.Label(window, textvariable=quantity_to_order)
chips_entry_fields[brand].append(order_label)
order_label.grid(row=row, column=3)
# Order button
button = tk.Button(window, text='Order', command=generate_order_quantities)
button.grid(row=3, column=3)
window.mainloop()

Related

Using if statements in Tkinter won't output on click

I am trying to make my first GUI app in python, and am very new to this. I want to make something for my wife (teacher) such that she can enter the number of "Growth Points" a student earns on a Standardized Test and spits out the number of "Gotchas" (in-class currency as incentives) the student receives.
The rules are: 3 gotchas for the first 6 growth points, then 5 gotchas for each subsequent growth point.
I was following a Guide from Geeksforgeeks to make the app, and can get the button to change text of a label already created but not output the Gotchas earned by the student... This is purely just to learn how to do it, so things like the Menu option is not necessary, just included as I learned.
Here is the code I have tried:
# Import Module
from tkinter import *
# create root window
root = Tk()
# root window title and dimension
root.title("Welcome to my first App")
# Set geometry (widthxheight)
root.geometry('450x300')
# all widgets will be here
# Determine the number of Growth Points
lbl = Label(root, text="How many Growth Points did the Student Earn?")
lbl.grid()
# Gather the number of Growth Points from the User
growth_points = Entry(root, width=10)
growth_points.grid(column=1, row=0)
menu = Menu(root)
item = Menu(menu)
item.add_command(label='New')
menu.add_cascade(label='File', menu=item)
root.config(menu=menu)
# Function for when the button is clicked
def clicked():
lbl.configure(text = "Clicked!") # Just to check whether the button does something
growth_points = int(growth_points)
if growth_points <= 6:
lbl_test = Label(root, text="Under 6") # Test to see if the if statement works
lbl_test.grid(column=0,row=2) # Output for the if statement (Doesn't work?)
num_gotcha = growth_points*3 # Gives the number of Gotchas for the first 6 growth points
num_gotcha = str(num_gotcha) # Converts into a String for Printing
gp_lbl = Label(root, text=num_gotcha) # Labels and inserts after clicking button
gp_lbl.grid(column=0,row=3)
elif growth_points > 6:
over_gotcha = growth_points - 6 # Gets the amount of Growth Points over the first 6
num_gotcha = over_gotcha * 5 + 18 # Finds the Gotchas for the GP's over 6, then adds the GPs for the first 6
num_gotcha = str(num_gotcha)
gp_lbl2 = Label(root, text="Student gets" + " " + num_gotcha + " " + "Gotchas") # Another way of trying to display the value
gp_lbl2.grid(column=0, row=3)
# Adding a button to begin the program
btn = Button(root, text = "Enter" , command=clicked)
btn.grid(column=2, row=0)
# Execute Tkinter
root.mainloop()
I can get the button to change the text, so it seems like the button works but it won't go through the If statements.. How should this work? Thanks for any help!
Didn't meddle with the "Gothcas" logic. Just added global reference to entry widget and renamed the variable for entered growth points:
# Function for when the button is clicked
def clicked():
global growth_points
lbl.configure(text="Clicked!") # Just to check whether the button does something
growth_points_number = int(growth_points.get())
if growth_points_number <= 6:
lbl_test = Label(root, text="Under 6") # Test to see if the if statement works
lbl_test.grid(column=0, row=2) # Output for the if statement (Doesn't work?)
num_gotcha = growth_points_number * 3 # Gives the number of Gotchas for the first 6 growth points
num_gotcha = str(num_gotcha) # Converts into a String for Printing
gp_lbl = Label(root, text=num_gotcha) # Labels and inserts after clicking button
gp_lbl.grid(column=0, row=3)
elif growth_points_number > 6:
over_gotcha = growth_points_number - 6 # Gets the amount of Growth Points over the first 6
num_gotcha = over_gotcha * 5 + 18 # Finds the Gotchas for the GP's over 6, then adds the GPs for the first 6
num_gotcha = str(num_gotcha)
gp_lbl2 = Label(root,
text="Student gets" + " " + num_gotcha + " " + "Gotchas") # Another way of trying to display the value
gp_lbl2.grid(column=0, row=3)
That should work.

Tkinter function creates variable that can't be added to list with .get() from entry

First off, I am aware that there are many questions out there with .get() not properly working and I have read through many of them.
My issue is that my program has a button that every time it is pressed, it adds a new entry box for a new Player to be added, then I then want to create a Player object (I have the player class defined in another file) and add the Player object to a list.
Here is the function for the "Add Player" button:
def add_player():
new_player = Label(team_wind, text='Enter an NBA Player:')
new_player_entry = Entry(team_wind, width=30)
new_player.pack()
new_player_entry.pack()
team_wind.mainloop()
player_list.append(new_player_entry.get())
(team_wind is the window because it is creating a Team of Players. the Team class is also defined in another file)
After some reaserch, I realized that mainloop would terminate that block of code (as far as my understanding). So, when I run the program, the entry returns ''. I know this is a very common issue, but it is wierd for mine because I can't figure out a way to get around it. I already have a working part of the tkinter window for a single player with a single entry box, so I know how .get() works. I've tried using .update() and .update_idletasks().
I have tried moving the player_list.append to before the mainloop
because I know the code after is unreachable, but if I move it to
before, then it returns nothing
. If I try to do it in another function or after the code terminates, then it won't work because if the button is pressed multiple times, each entry will have the same variable name. I think this means it has to be done in this function, but not sure how to do that with mainloop. Here is the code to create the window so it will run. All I need is for it to be able to print or return the list with the player objects for however many players there are (depends how many times the button is pressed).
I have provided the code needed to run the program and cut out everything else that would cause errors. Thanks
from tkinter import *
player_list = []
def add_player():
new_player = Label(team_wind, text='Enter an NBA Player:')
new_player_entry = Entry(team_wind, width=30)
new_player.pack()
new_player_entry.pack()
team_wind.mainloop()
player_list.append(new_player_entry.get())
def main():
#setup
global team_name
global team_wind
global player_entry
global player_entry2
team_wind = Tk()
team_wind.title('Team')
team_wind.geometry('800x500')
#Text entries
team_mode_title = Label(team_wind, text='Make a team of NBA Players and find their stats')
player = Label(team_wind, text='Enter an NBA Player:')
player2 = Label(team_wind, text='Enter an NBA Player:')
#Text Box Entries
player_entry = Entry(team_wind, width=30)
player_entry2 = Entry(team_wind, width=30)
#BUTTONS
add_player_button = Button(team_wind, text='Add player', command=add_player)
#Button for StackOverflow question to print the list of players
print_list_button = Button(team_wind, text='Print List', command=print_list)
#Pack
team_mode_title.pack()
#avg_button.pack()
add_player_button.pack()
print_list_button.pack()
player.pack()
player_entry.pack()
player2.pack()
player_entry2.pack()
team_wind.mainloop()
def print_list():
player_list.append(player_entry.get())
player_list.append(player_entry2.get())
print(player_list)
if __name__ == "__main__":
main()
You should have only one mainloop() - in main().
You could use your list to keep widgets Entry - without using .get() - and you should use .get() when you print list.
Minimal working code.
I used Frame to keep Label and Entry and this way it adds Entry before Buttons.
import tkinter as tk # PEP8: `import *` is not preferred
def add_player():
label = tk.Label(frame, text='Enter an NBA Player:')
label.pack()
entry = tk.Entry(frame, width=30)
entry.pack()
entry_list.append( entry ) # add full widget, not value from widget
def print_list():
for number, entry in enumerate(entry_list, 1):
print( number, entry.get() )
def main():
global team_wind
global frame
team_wind = tk.Tk()
team_wind.title('Team')
team_wind.geometry('800x500')
team_mode_title = tk.Label(team_wind, text='Make a team of NBA Players and find their stats')
team_mode_title.pack()
# frame to keep all Entries
frame = tk.Frame(team_wind)
frame.pack()
# at start add Entries for two players
add_player()
add_player()
# button to add Entry for next player
add_player_button = tk.Button(team_wind, text='Add player', command=add_player)
add_player_button.pack()
# button to print values from all Entries
print_list_button = tk.Button(team_wind, text='Print List', command=print_list)
print_list_button.pack()
team_wind.mainloop()
# --- main ---
entry_list = []
main()
Once your program reaches the team_wind.mainloop() command nothing under it will execute because python will continute running mainloop() forever. This means that the function will never get to running player_list.append(Player(new_player_entry.get())).

Tkinter - Creating multiple check boxes using a loop

I am trying to create a program that allows the user to select any number of check boxes and hit a button to return a random result from those check boxes. Since I am basing my list off the roster of Smash bros ultimate, I am trying to avoid creating 70+ variables just to place check boxes. However, I am unable to figure out how to iterate this. The various values set for rows are just placeholders until I can figure this out. I would also like to have a reset button at the top that allows the user to automatically uncheck every box. This code is what I have so far. Any help would be greatly appreciated.
#!/usr/bin/python3
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
f = [] #check boxes
ft = open("Fighters.txt").readlines() #list of all the character names
fv=[0]*78 #list for tracking what boxes are checked
ff=[] #list to place final character strings
def reset():
for i in fv:
fv[i]=0
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for y in range (0,77):
f[y] = Checkbutton(window, text = ft[y], variable = fv[y])
f[y].grid(column=0, row=4+y)
def done():
for j in fv:
if fv[j] == 1:
ff.append(fv[j])
result = random.choice(ff)
r=Label(window, text=result)
d = Button(window, text="Done", command=done)
d.grid(column=0, row = 80)
window.mainloop()
Unfortunately I'm afraid you are going to have to create variables for each checkbox.
tkinter has special purpose Variable Classes for holding different types of values, and if you specify an instance of one as the variable= option when you create widgets like Checkbutton, it will automatically set or reset its value whenever the user changes it, so all your program has to do is check its current value by calling its get() method.
Here's an example of the modifications to your code needed to create them in a loop (and use them in the done() callback function):
import random
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
with open("Fighters.txt") as fighters:
ft = fighters.read().splitlines() # List of all the character names.
fv = [BooleanVar(value=False) for _ in ft] # List to track which boxes are checked.
ff = [] # List to place final character strings.
def reset():
for var in fv:
var.set(False)
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for i, (name, var) in enumerate(zip(ft, fv)):
chk_btn = Checkbutton(window, text=name, variable=var)
chk_btn.grid(column=0, row=i+4, sticky=W)
def done():
global ff
ff = [name for name, var in zip(ft, fv) if var.get()] # List of checked names.
# Randomly select one of them.
choice.configure(text=random.choice(ff) if ff else "None")
d = Button(window, text="Done", command=done)
d.grid(column=0, row=len(ft)+4)
choice = Label(window, text="None")
choice.grid(column=1, row=3)
window.mainloop()
I wasn't sure where you wanted the Label containing the result to go, so I just put it to the right of the Reset button.
variable = fv[y]
This looks up the value of fv[y] - i.e, the integer 0 - at the time the Checkbutton is created, and uses that for the variable argument.
You need to use an instance of one of the value-tracking classes provided by TKinter, instead. In this case we want BooleanVar since we are tracking a boolean state. We can still create these in a list ahead of time:
text = open("Fighters.txt").readlines()
# Let's not hard-code the number of lines - we'll find it out automatically,
# and just make one for each line.
trackers = [BooleanVar() for line in text]
# And we'll iterate over those pair-wise to make the buttons:
buttons = [
Checkbutton(window, text = line, variable = tracker)
for line, tracker in zip(text, trackers)
]
(but we can not do, for example trackers = [BooleanVar()] * len(text), because that gives us the same tracker 78 times, and thus every checkbox will share that tracker; we need to track each separately.)
When you click the checkbox, TKinter will automatically update the internal state of the corresponding BooleanVar(), which we can check using its .get() method. Also, when we set up our options for random.choice, we want to choose the corresponding text for the button, not the tracker. We can do this with the zip trick again.
So we want something more like:
result_label = Label(window) # create it ahead of time
def done():
result_label.text = random.choice(
label
for label, tracker in zip(text, trackers)
if tracker.get()
)

Tkinter RadioButton printing previous result in the list box

I am fairly new to Python. I have a simple program that has 3 products, 3 milk options and 3 sizes and an add button, all as radio buttons. The user selects the product, the size, the milk and presses add. The radio-button has values associated to them, e.g. my cappuccino button has value="cappuccino" and my small button has value="small". Upon adding, the values are written to a database and the databases contents are updated to a list-box tkinter widget; so it looks like an order.
My problem is that if i select a certain product, size, and milk type, it writes these details to the database and prints to the list-box, but if select a completely different order it may and add it, it'll write the previous order a couple times to the database as i press add, and then it'll write the correct order.
Any way to fix this so each order is printed as it is. Could it possibly be to the variables storing the previous values. Please can someone provide a solution.
The code below is not everything but gives you a jist of the structure.
def view_command():
self.orderList.delete(0,END) #listbox on screen 2
for row in backend.view():
self.orderList.insert(END,row)
def addItem():
view_command()
choice = v.get()
size = s.get()
milkOptions = m.get()
backend.customerOrders(choice,size,milkOptions)
self.orderList = tk.Listbox(self,width=112)
self.orderList.place(x=0,y=680)
#Scrollbar for Order Listbox
self.scrollbar = tk.Scrollbar(self)
self.scrollbar.place(x=700,y=685)
#Scroll bar to list box configure
self.orderList.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.orderList.yview)
#Americano Button
self.americanoImage = tk.PhotoImage(file="ResizedItemImages/HotDrinks/americanoResized.png")
#self.americanoButton = tk.Radiobutton(self,image=self.americanoImage, command=lambda: product("Americano"),variable=v)
self.americanoButton = tk.Radiobutton(self,image=self.americanoImage,variable=v, value="Americano")
#Cappucino Button
self.cappucinoImage = tk.PhotoImage(file="ResizedItemImages/HotDrinks/cappucinoResized.png")
#self.cappucinoButton = tk.Radiobutton(self,image=self.cappucinoImage, command=lambda: product("Cappucino"),variable=v)
self.cappucinoButton = tk.Radiobutton(self,image=self.cappucinoImage,variable=v, value="Cappucino")
#Espresso Button
self.espressoImage = tk.PhotoImage(file="ResizedItemImages/HotDrinks/espressoResized.png")
#self.espressoButton = tk.Radiobutton(self,image=self.espressoImage, command=lambda: product("Cappucino"),variable=v)
self.espressoButton = tk.Radiobutton(self,image=self.espressoImage,variable=v, value="Espresso")
#Add button
self.addImage = PhotoImage(file="ResizedItemImages/Function/addResized.png")
self.addButton = Button(self,image=self.addImage,command=addItem)
self.addButton.place(y=550,x=440)
#Small button
self.smallImage = tk.PhotoImage(file="ResizedItemImages/Function/smallResized.png")
self.smallButton = tk.Radiobutton(self,image=self.smallImage,
variable=s, value="Small")
self.smallButton.place(y=550,x=0)
#Medium Button
self.mediumImage = tk.PhotoImage(file="ResizedItemImages/Function/medium_Resized.png")
self.mediumButton = tk.Radiobutton(self,image=self.mediumImage,
variable=s, value="Medium")
self.mediumButton.place(y=550,x=140)
#Large button
self.largeImage = tk.PhotoImage(file="ResizedItemImages/Function/largeResized.png")
self.largeButton = tk.Radiobutton(self,image=self.largeImage,
variable=s, value="Large")
self.largeButton.place(y=550,x=290)
#ALL MILK OPTION BUTTONS
#Soya Milk Button
self.soyaMilkImage = tk.PhotoImage(file="ResizedItemImages/MilkOptions/soyaMilkResized.png")
self.soyaMilkButton = tk.Radiobutton(self, image=self.soyaMilkImage,variable=m, value="Soya")
#Cocounut Milk Button
self.coconutMilkImage = tk.PhotoImage(file="ResizedItemImages/MilkOptions/coconutMilkResized.png")
self.coconutMilkButton = tk.Radiobutton(self, image=self.coconutMilkImage,variable=m, value="Coconut")
#Whole Milk
self.wholeMilkImage = tk.PhotoImage(file="ResizedItemImages/MilkOptions/wholeMilkResized.png")
self.wholeMilkButton = tk.Radiobutton(self, image=self.wholeMilkImage,variable=m, value="Whole")

Tkinter - Update changes in variables in previously generated widgets, from dictionary

I need to represent in "ventana2" the pairs entered in "ventana", so that a new frame appears when the key is new. When the key already exists in the dictionary, I need to change the old value in the frame created for that key previously (the new value is adding old and new).
I can not get the frames permanently related to my dictionary partner, through the key.
Thank you very much in advance, and sorry for my english.
Here is a summary of the code:
import tkinter as tk
ventana = tk.Tk()
ventana2 = tk.Tk()
name = tk.StringVar()
tk.Entry(ventana, textvariable=name, width=30).grid(row=0, column=1)
tk.Label(ventana, text = 'Nombre').grid(row=0, column=0)
value = tk.StringVar()
tk.Entry(ventana, textvariable=value, width=30).grid(row=1, column=1)
tk.Label(ventana, text = 'Celular').grid(row=1, column=0)
contactos={}
def intro():
nom = name.get()
if nom in contactos:
cel = contactos[nom] + float(value.get())
contactos[nom] = cel
else:
cel = float(value.get())
contactos[nom] = cel
create_widget()
def create_widget():
frame = tk.Frame(ventana2)
frame.pack()
nomb = tk.Label(frame, text=name.get()).pack(side=tk.LEFT)
telf = tk.Label(frame, text=contactos[name.get()]).pack(side=tk.RIGHT)
intro_btn = tk.Button(ventana, text='Intro', command = intro)
intro_btn.grid(row=2, column=0, columnspan=2, sticky = 'ew')
ventana.mainloop()
Labels created inside the create_widget() function is in the function scope. After the function ends all references to the label is lost. So you need to think of a way to save references to the label (suggest return statement) and associate them to the dictionary of contactos.
Update - relate a frame with a value
Save a reference to the object you wish to remember and the name you want to use to recall it in a list(or dict or tuple et.). Then append all that to your global list of widgets. For example:
nomb = tk.Label( ... )
widget_parameters = ['Nomb Label', nomb]
global_widget_list.append(widget_parameters)
Then you can search the global_widget_list for any widget you have named and get a referance to that widget.
I have included some example code to illustrate one way that you can accomplish that. Play around with it until you understand it and you will be able to implement it in your own application.
from tkinter import *
import time
root = Tk()
root.geometry('300x200')
global_widget_list = [] # List for holding all widgets
def make_label(): # Create label
text_label = Label(root,text='Text input')
text_label.pack()
global_widget_list.append(['Text Label',text_label]) # Add widget to list
def make_input(): # Create entry
inputvar = StringVar()
text_input = Entry(root,width=20,textvariable=inputvar)
text_input.pack()
global_widget_list.append(['Text Input',text_input,inputvar]) # Add widget to list
make_label() # Run functions to cretae GUI
make_input()
root.update() # Take care of updating GUI
time.sleep(3) # Wait so you have time to see the original, then change it
# Now, loop through all widgets and do what you want
for widget in global_widget_list:
widget_name = widget[0]
widget_id = widget[1]
print(widget_name, 'has identity', widget_id)
if widget_name == 'Text Label':
print('Changing test label text to: ALL CAPS')
widget_id.configure(text='ALL CAPS')
if widget_name == 'Text Input':
print('Changing text in entry to: SAMPLE')
var = widget[2]
var.set('SAMPLE')
root.update() # Take care of updating GUI
time.sleep(3) # Wait so you have time to see the changes, then change it
print('Removing Entry from application GUI')
global_widget_list[1][1].pack_forget() # Remove entry form GUI

Categories

Resources