I am trying to make a GUI that have two dropdown menus and a text label.
The value selected in the first dropdown menu should update the list of options in the second dropdown menu. (this part of the code works correctly!).
Then, once the user select a value from the second dropdown menu the text label should update. I am trying to update the text label with a using the config method, but it does not seem to work. any ideas?
import tkinter as tk
from tkinter import *
root = Tk()
root.title("My app")
root.minsize(width=330,height=280)
options1 = ["Option 1","Option 2"]
options2 = [""]
options21 =["Option 1.1","Option 1.2","Option 1.3","Option 1.4"]
options22 = ["Option 2.1","Option 2.2","Option 2.3","Option 2.4"]
labelvuot=Label(root,text= " ")
def weigthtxt(event):
if om2.get() == "":
pass
else:
mydamage = int(om2.get().split(".")[1])
if mydamage == 1:
labelweigth.config(text="1")
elif mydamage == 2:
labelweigth.config(text="2")
elif mydamage == 3:
labelweigth.config(text="3")
elif mydamage == 4:
labelweigth.config(text="4")
else:
labelweigth.config(text="No number")
def add_option(self):
om2.set("")
labelweigth.config(text="")
answer = om1.get()
global options2
options2.clear()
if answer == "Option 1":
options2 = options2+options21
menu = drop2["menu"]
menu.delete(0, "end")
for x in options2:
menu.add_command(label=x,
command=lambda value=x: om2.set(value))
elif answer == "Option 2":
options2 = options2 + options22
menu = drop2["menu"]
menu.delete(0, "end")
for x in options2:
menu.add_command(label=x,
command=lambda value=x: om2.set(value))
def save():
element = om1.get()
damage = om2.get()
inten = r1.get()
exten = r2.get()
pergiu = r3.get()
txt = note.get(1.0,END)
print(element,damage,inten,exten,pergiu,txt,int(om2.get().split(".")[1]))
label1=Label(root,text= "Select Element").grid(row=1,column=3)
labelvuot.grid(row=1,column=5)
om1 = tk.StringVar()
om1.set("")
drop = tk.OptionMenu(root, om1, *options1, command= add_option)
drop.config(width=20)
drop.grid(row=1,column=7,columnspan=3)
label2=Label(root,text= "Damage Type").grid(row=2,column=3)
om2 = tk.StringVar()
om2.set("")
drop2 = tk.OptionMenu(root,om2, *options2,command=weigthtxt)
drop2.config(width=20)
drop2.grid(row=2,column=7,columnspan=3)
label3=Label(root,text= "Weigth").grid(row=3,column=1,columnspan=3)
labelweigth = Label(root,text="")
labelweigth.grid(row=3,column=7,columnspan=3)
root.mainloop()
tkinter module has an internal class _setit which is used by tk.OptionMenu class when setting up the menu actions.
You need to use this internal class when you populate the menu items inside add_option() function:
...
# it is executed when item of second dropdown menu is selected
def weigthtxt(value):
if value:
mydamage = int(value.split(".")[1])
if mydamage == 1:
labelweigth.config(text="1")
elif mydamage == 2:
labelweigth.config(text="2")
elif mydamage == 3:
labelweigth.config(text="3")
elif mydamage == 4:
labelweigth.config(text="4")
else:
labelweigth.config(text="No number")
def add_option(answer):
om2.set("")
labelweigth.config(text="")
options2.clear()
if answer == "Option 1":
options2.extend(options21)
elif answer == "Option 2":
options2.extend(options22)
menu = drop2["menu"]
menu.delete(0, "end")
for x in options2:
menu.add_command(label=x, command=tk._setit(om2, x, weigthtxt))
...
So I'm making a program which calls a function to do some calculations, and there are buttons to press to change the inputs and recalculate.
It works great, except if a button is pressed before the calculations are finished, the new values are calculated and outputted before returning back to the previous values. Basically, the program does what I want, except it returns to the first calculations after button press and completing second calculations (all the variable values return).
A general schematic of the problem:
1. Root mainloop
2. Values inputted
3. Press 'Go'
4. Calculations printed to screen (Monty Carlo sim by the way)
5. Press button to change input values
6. New calculations printed to screen
7. Once new ones finish, calculations for old variable values return until completion
Is there a way to prevent python from returning to the previous variable values like this? My hope is that there is a way to return to the mainloop so that the calculations on the screen stay correct. Thanks!
Edit:
Here's the code (Sorry it's a bit long and this is my first time with tkinter):
from tkinter import *
from tkinter import ttk
from random import *
from statistics import *
from math import *
'''
Prompt user for: number attacking with, number of defenders at each space
Output: n approaching 100000 and error range approaching .3%, probabilities of next roll, probabilities of winning at each spot, average number of pieces lost & stddev,buttons for decreasing defender/attacker
'''
def reset():
attacker_entry.delete(0,END)
for i in range(len(spaces_entry)):
spaces_entry[i].delete(0,END)
update_buttons()
def update_buttons(*args):
buttons = [attacker_2,attacker_1,split_button,defender_1,defender_2]
try:
if int(attacker_entry.get()) > 1:
for button in buttons:
button['state'] = ['normal']
elif int(attacker_entry.get()) == 1:
attacker_2['state'] = ['disabled']
split_button['state'] = ['disabled']
elif int(attacker_entry.get()) == 0:
for button in buttons:
button['state'] = ['disabled']
return
if int(space1_entry.get()) > 1:
defender_2['state'] = ['normal']
elif int(space1_entry.get()) == 1:
defender_2['state'] = ['disabled']
split_button['state'] = ['disabled']
except:
for button in buttons:
button['state'] = ['disabled']
def subtract(label,*args):
if label == "both":
label = "att def 1"
end = int(label[-1:])
if "att" in label:
attacker_amount.set(int(attacker_entry.get()) - end)
if "def" in label:
space1.set(int(space1_entry.get()) - end)
if int(space1_entry.get()) == 0:
attacker_amount.set(int(attacker_entry.get()) - 1)
for i in range(len(spaces)):
try:
spaces[i].set(int(spaces_entry[i+1].get()))
except:
spaces[i].set("")
win_avgs[i].set("")
pieces_left[i].set("")
most_likely[i].set("")
space10.set("")
update_buttons()
go()
def check_if_multiple(list1,list2):
if len(list1)>1 and len(list2)>1:
ret_val = 2
else:
ret_val = 1
return ret_val
def set_rolls(total,limit):
list = []
for i in range(total):
if len(list) < limit:
list.append(randrange(1,7))
return list
def go(*args):
update_buttons()
try:
attacker_total = int(attacker_entry.get())
except:
return
defender_pieces_list = []
for entry in spaces_entry:
try:
new_val = int(entry.get())
if new_val == 0:
None
else:
defender_pieces_list.append(new_val)
except:
None
defender_total_spaces = len(defender_pieces_list)
attacker_total_original = attacker_total
total_trials = 10000
defender_losses = 0
attacker_losses = 0
first_round_defender_total_wins = 0
first_round_attacker_total_wins = 0
first_round_split_total = 0
space1_succ,space2_succ,space3_succ,space4_succ,space5_succ,space6_succ,space7_succ,space8_succ,space9_succ,space10_succ = [],[],[],[],[],[],[],[],[],[]
space1_all,space2_all,space3_all,space4_all,space5_all,space6_all,space7_all,space8_all,space9_all,space10_all = [],[],[],[],[],[],[],[],[],[]
succ_list = [space1_succ,space2_succ,space3_succ,space4_succ,space5_succ,space6_succ,space7_succ,space8_succ,space9_succ,space10_succ]
all_list = [space1_all,space2_all,space3_all,space4_all,space5_all,space6_all,space7_all,space8_all,space9_all,space10_all]
for trial in range(total_trials):
if trial%20 == 0:
for i in range(0,defender_total_spaces):
try:
win_avgs[i].set(round(((len(succ_list[i]))/trial)*100,1))
pieces_left[i].set(str(round(mean(all_list[i]),2)))
most_likely[i].set(mode(all_list[i]))
root.update()
except:
None
attacker_total = attacker_total_original
first_round = True
for i in range(defender_total_spaces):
defender_total = defender_pieces_list[i]
while defender_total>0 and attacker_total > 0:
defender_win = False
attacker_win = False
defender_rolls_list = set_rolls(defender_total,2)
attacker_rolls_list = set_rolls(attacker_total,3)
if len(attacker_rolls_list) == 1:
defender_rolls_list = [randrange(1,7)]
for j in range(check_if_multiple(defender_rolls_list,attacker_rolls_list)):
if max(defender_rolls_list)>=max(attacker_rolls_list):
attacker_total += -1
defender_win = True
else:
defender_total += -1
attacker_win = True
attacker_rolls_list.remove(max(attacker_rolls_list))
defender_rolls_list.remove(max(defender_rolls_list))
if first_round == True:
if defender_win == True and attacker_win == True:
first_round_split_total += 1
elif attacker_win == True:
first_round_attacker_total_wins += 1
elif defender_win == True:
first_round_defender_total_wins += 1
first_round = False
if defender_total == 0:
succ_list[i].append(attacker_total)
all_list[i].append(attacker_total)
if attacker_total == 1:
attacker_total == -1
if attacker_total == 0:
all_list[i].append(attacker_total)
attacker_total += -1
for i in range(0,defender_total_spaces):
try:
win_avgs[i].set(round(((len(succ_list[i]))/trial)*100,1))
pieces_left[i].set(str(round(mean(all_list[i]),2))+"("+str(round(stdev(all_list[i]),1))+")")
most_likely[i].set(mode(all_list[i]))
except:
None
height = 450
width = 600
shape = str(width) + "x" + str(height)
root = Tk()
root.title("Risk Probability Calculator")
root.geometry(shape)
content = ttk.Frame(root)
content.grid(column=0, row=0, sticky=(N, W, E, S))
content.columnconfigure(0, weight=1)
content.rowconfigure(0, weight=1)
title = ttk.Label(content, text="Risk Probability Calculator", relief="ridge", background="gray",font=("TkHeadingFont",20),anchor="center")
title.grid(column=1, row=1, columnspan=8, padx=3, pady=4,sticky=(N,W,E,S))
reset_button = ttk.Button(content, text="Reset", command=reset)
reset_button.grid(column=1, row=2,padx=3, pady=4, sticky=(N, W, E, S))
go_button = ttk.Button(content, text="Go", command=go)
go_button.grid(column=2, row=2,padx=3, pady=4, sticky=(N, W, E, S))
ttk.Label(content, text="Attacking with:").grid(column=1, row=3,padx=3, pady=4, sticky=(NW))
ttk.Label(content, text="Defending with:").grid(column=1, row=4,padx=3, pady=4, sticky=(NW))
for i in range(5,15):
text = "Space " + str(i-4) + ":"
ttk.Label(content, text=text).grid(column=1,padx=3, pady=4, row=i, sticky=(N,E,S))
attacker_amount = StringVar()
attacker_entry = ttk.Entry(content, textvariable=attacker_amount, width=4)
attacker_entry.grid(column=2, row=3,padx=3, pady=4, sticky=(N, W, S))
spaces = []
spaces_entry = []
for i in range(1, 11):
globals()['space'+str(i)] = StringVar()
spaces.append(globals()['space'+str(i)])
globals()['space'+str(i)+'_entry'] = ttk.Entry(content, textvariable=spaces[i-1], width=4)
globals()['space'+str(i)+'_entry'].grid(column=2, row=(i+4),padx=3, pady=4, sticky=(N, W, S))
spaces_entry.append(globals()['space'+str(i)+'_entry'])
attacker_2 = Button(content, text="Attacker -2",command=lambda: subtract("att 2"))
attacker_2.grid(column=4, row=2,padx=3, pady=4, sticky=(N,W,E,S))
attacker_1 = Button(content, text="Attacker -1",command=lambda: subtract("att 1"))
attacker_1.grid(column=5, row=2,padx=3, pady=4, sticky=(N,W,E,S))
split_button = Button(content, text="Split",command=lambda: subtract("both"))
split_button.grid(column=6, row=2,padx=3, pady=4, sticky=(N,W,E,S))
defender_1 = Button(content, text="Defender -1",command=lambda: subtract("def 1"))
defender_1.grid(column=7, row=2,padx=3, pady=4, sticky=(N,W,E,S))
defender_2 = Button(content, text="Defender -2",command=lambda: subtract("def 2"))
defender_2.grid(column=8, row=2,padx=3, pady=4, sticky=(N,W,E,S))
ttk.Separator(content,orient="vertical").grid(column=3,row=2,rowspan=15,padx=3, pady=4,sticky=(N,W,E,S))
results_frame = ttk.Labelframe(content, text='Results:')
results_frame.grid(column=4, row=3, columnspan=5, rowspan=12,padx=3, pady=4, sticky=(N,W,E,S))
pane = ttk.Panedwindow(results_frame, orient='horizontal')
pane.grid(column=1,row=1,columnspan=5,padx=3,sticky=(N,W,E,S))
pane1 = ttk.Labelframe(pane)
pane.add(pane1)
Label(pane1,text="% Win").grid(column=1,row=1,sticky=(N,W,E))
win_avgs = []
for i in range(1,11):
globals()['win_avg'+str(i)] = StringVar()
win_avgs.append(globals()['win_avg'+str(i)])
Label(pane1,textvariable=win_avgs[i-1]).grid(column=1,row=i+1,padx=4, pady=4,sticky=(N,W,S))
pane2 = ttk.Labelframe(pane)
pane.add(pane2)
Label(pane2,text="Pieces Left").grid(column=1,row=1,sticky=(N,W,E))
pieces_left = []
for i in range(1,11):
globals()['pieces_left'+str(i)] = StringVar()
pieces_left.append(globals()['pieces_left'+str(i)])
Label(pane2,textvariable=pieces_left[i-1]).grid(column=1,row=i+1,padx=4, pady=4,sticky=(N,W,S))
pane3 = ttk.Labelframe(pane)
pane.add(pane3)
Label(pane3,text="Most likely # of Pieces Left").grid(column=1,row=1,sticky=(N,W,E))
most_likely = []
for i in range(1,11):
globals()['most_likely'+str(i)] = StringVar()
most_likely.append(globals()['most_likely'+str(i)])
Label(pane3,textvariable=most_likely[i-1]).grid(column=1,row=i+1,padx=4, pady=4,sticky=(N,W,S))
root.mainloop()
The short answer is that it's because you call root.update(). This not only redraws the screen, but processes any pending events. A good rule of thumb is "never call update" for this exact reason. Infrequently you simply must, but usually there's a better way to organize your code. You can sometimes get away with it if you do it in a function that only takes a few ms, but it looks like your function computes a lot of things. If a user presses a key or clicks a button and then you call update, tkinter is going to try to process that event before continuing.
I would recommend first just removing the call and see what happens. If the screen seems to freeze (which it might, given how much code you're trying to run), you can try replacing it with root.update_idletasks(). That won't process any click or button events, only a certain class of event such as "repaint the screen".