Create Functions for New Windows in Python - python

I have a function that creates a new window and updates some values to insert into one of the Entry widgets in the new window, as part of a times table quiz. I then need to create a new function that runs when the submit button is clicked and checks that the user's answer is correct. However, this new function gives me a NameError when it runs because it cannot identify that widgets in the new window, only checking the widgets in the original window. How do I get it to check the widgets in the new window?
Original window:
Label(window,text="TIMES TABLES").pack()
game_btn = Button(window, text="Play Game",command=playGameClicked)
game_btn.pack()
Function that creates new window:
def playGameClicked():
#create new window
newWindow = Toplevel(window)
newWindow.geometry("500x300+0+0")
Label(newWindow, text = "Times Tables Game").pack()
Label(newWindow,text="Question:",font=("Helvetica",10),width=10,).pack()
question_box = Entry(newWindow,width=30,background="light blue")
question_box.pack()
Label(newWindow,text="Your Answer:",font=("Helvetica",10),width=10).pack()
answer_box = Entry(newWindow,width=50,background="light green")
answer_box.pack()
submit_btn = Button(newWindow,text="Submit",width=10,command=submitClicked)
submit_btn.pack()
Label(newWindow,text="You are...",font=("Helvetica",10),width=10).pack()
correction_box = Text(newWindow,width=15,height=1,background="light blue")
correction_box.pack()
#new question (all_tables_dict is a long dictionary of 1-12xtables e.g"1*2":2)
question_box.delete(0,END)
keys = list(all_tables_dict.keys())
new_question = random.choice(keys)
question_box.insert(END, new_question)
When the submit button is clicked: (this is the function that doesn't work)
def submitClicked():
answer_box.get(0.0,END)
try:
answer = all_tables_dict[question]
correct=True
except:
correct=False
if correct==True:
correction_box.insert("CORRECT!")
else:
correction_box.insert("INCORRECT!")

You didn't show full error message and I can't run it so I will guess
All variables created inside function are local and they can't be accessed in other functions. You have to inform function that it has to assign widget to global variable.
def playGameClicked():
global answer_box
global correction_box
# ... code ...
answer_box = ...
# ... code ...
correction_box = ...
# ... code ...

Related

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())).

Why does my selection callback get called on my second Listbox?

from tkinter import *
from tkinter.ttk import *
root = Tk()
listbox = None
listboxMultiple = None
listboxStr = None
listboxMultipleStr = None
def main():
global root
global listboxStr
global listboxMultipleStr
global listbox
global listboxMultiple
root.protocol("WM_DELETE_WINDOW", exitApplication)
root.title("Title Name")
root.option_add('*tearOff', False) # don't allow tear-off menus
root.geometry('1600x300')
listboxStr = StringVar()
listboxStr.set("ABCD")
listbox = Listbox(root, name="lb1", listvariable=listboxStr, width=120)
listbox.pack(side=LEFT)
listbox.bind("<<ListboxSelect>>", selectListItemCallback)
listboxMultipleStr = StringVar()
listboxMultipleStr.set("")
listboxMultiple = Listbox(root, name="lb2", listvariable=listboxMultipleStr, width=120)
listboxMultiple.pack(side=LEFT)
root.mainloop()
def selectListItemCallback(event):
global listboxMultipleStr
global listbox
global listboxMultiple
print("event.widget is {} and listbox is {} and listboxMultiple is {}\n".format(event.widget, listbox, listboxMultiple))
selection = event.widget.curselection()
listboxMultipleStr.set("")
if selection:
index = selection[0]
data = event.widget.get(index)
newvalue = "{}\n{}".format(data,"SOMETHING")
print("selected \"{}\"\n".format( data ))
print("newvalue is \"{}\"\n".format( newvalue ))
listboxMultiple.insert(END, "{}".format(data))
listboxMultiple.insert(END, "SOMETHING")
#listboxMultipleStr.set( newvalue )
else:
pass
def exitApplication():
global root
root.destroy()
if __name__ == "__main__":
main()
Using python3 on Windows 7. I've setup a callback "selectListItemCallback" for one of my two listbox widgets. And yet, when I click on the text in "lb1" it works as expected, I update "lb2" with the same selected text plus I add another line to "lb2".
The issue is, when I then select the item in "lb2", it still calls the callback and the event.widget is "lb1" and not "lb2".
My intent is to have a list of items in 'lb1' and when I select any of them, then 'lb2' gets filled with info related to the selected 'lb1' item. And, I don't want a selection callback invoked in my 'lb2' widget.
Can you see what I'm doing wrong that could be causing this strange behavior?
Thank you.
I've posted the code; and this does run on my Windows 7 machine using python3. Python 3.8.6 to be exact.
It is because the event fires when the first list box loses the selection when you click on the other list box. The event fires whenever the selection changes, not just when it is set.
If you don't want the first listbox to lose its selection when you click in the second listbox, set exportselection to False for the listboxes. Otherwise, tkinter will only allow one to have a selection at a time.

Python tkinter updating canvas label outside the main function

I am trying to update the canvas label outside the function that canvas was created.
def Application_GUI():
global Scanned_serial
global label1
global label2
global window
global label_gaminys
global canvas_tk
window = Tk() # create a GUI window
window.geometry("1920x1080") # set the configuration of GUI window
canvas_tk = Canvas(window,bg='ivory2',width=1920,height=1080)
canvas_tk.pack()
label1=Label(canvas_tk,text = "SKENUOKITE BARKODA(GUID) ARBA DAIKTO RIVILINI KODA:",bg='ivory2')
entry = Entry(canvas_tk) # entry = guid
canvas_tk.create_window(960,50,window=label1)
canvas_tk.create_window(960,100,window=entry)
var = IntVar()
button = Button(canvas_tk,text="Testi operacija",width = 30,height=2,command = lambda: var.set(1))
button2 = Button(canvas_tk,text="RESTART DEVICES",width = 30,height=2,command = lambda:restart_devices(myConnection))
ota_button = Button(canvas_tk, text="OTA", width=30, height=1, command=OTA_gui)
canvas_tk.create_window(960,150,window=button)
canvas_tk.create_window(960,200,window=button2)
canvas_tk.create_window(960,250,window=ota_button)
# ********************* IMPORTANT PART *************************
label_gaminys=Label(canvas_tk,text = "GAMINIO KODAS:",bg='ivory2')
canvas_tk.create_window(960,450,window=label_gaminys)
# ***************************************************************
print("waiting...")
button.wait_variable(var)
result = entry.get()
print("result=",result)
Scanned_serial = entry.get()
label2=Label(window,text = "Vykdoma operacija:")
label2.pack()
window.update()
In the function above, I am creating my user interface using a canvas. The important line of code is there:
label_gaminys=Label(canvas_tk,text = "GAMINIO KODAS:",bg='ivory2')
canvas_tk.create_window(960,450,window=label_gaminys)
I have created a window for a text at location 960,450.
I want to update this label outside this GUI function during a operation .
def Full_operation():
#Destroy previous window
window.destroy()
#create a new GUI window
Application_GUI()
global canvas_tk
operacijos_kodas=Scanning_operation(myConnection,Scanned_serial)
elif(operacijos_kodas == 1):
insertData_komplektacija(myConnection,"fmb110bbv801.csv");
update_current_operation(myConnection);
#label2.config(text = "Take items from the box:")#update the label2
label_gaminys=Label(canvas_tk,text = "Gamninio kodas=%s"%(Scanned_serial),bg='ivory2')
canvas_tk.create_window(960,450,window=label_gaminys)
picking_operation(myConnection,label2);
The function above describes the operatio. I want to modify the label inside this function. I have described my canvas_tk as global and initialise in this function so I can access and modify it. I have managed to update the label by creating a new window as following:
label_gaminys=Label(canvas_tk,text = "Gamninio kodas=%s"%(Scanned_serial),bg='ivory2')
canvas_tk.create_window(960,450,window=label_gaminys)
picking_operation(myConnection,label2);
But that does not seem like a correct way to do that since I am not actually "updating" the label, instead I am creating a new window and assigning it to a new label.
Could someone give me some general advice on how to do that properly?
You can just create a function that does this:
label_gaminys["text'] = "" #Insert the text you want into it and then the text changes
and then you can modify it whilst being in the same window. On another note, do notice that your Full_operation has an elif in it without an if first (just notifying you, because if there is no if before it it will cause an error).

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

Trying to produce function for a custom number of tkinter Entry fields.

I've been trying to create a piece of code that would take a integer as a argument and create that number of tkinter entry fields. With a submit button at the end that would retrieve the data from the fields add these data to a list then close the window.
I have been able to get it working however I cant find a way to convert this to a callable function; a requirement to use it with the rest of my program.
This is the code I have produced so far, thanks:
import tkinter as tk
b = input("Enter: ")
b = int(b)
root = tk.Tk()
newdict = dict()
outputs = list()
for i in range(b):
newdict["entry" + str(i)] = tk.Entry(root)
newdict["entry" + str(i)].pack()
button1 = tk.Button(root, text="Submit", command=lambda: Get(newdict))
button1.pack()
def Get(newdict):
for j in range(b):
outputs.append(newdict["entry" + str(j)].get())
root.quit()
root.mainloop()
print(outputs)
The basic idea is to create a window, then use the wait_window method to wait for the window to be destroyed. Once it has been destroyed you can return some value.
The problem is that the values you want to fetch must not be attributes of the window, since it will have been destroyed by the time you are ready to fetch them. You need to set up your code to save the values before the window is destroyed.
A simple way is to provide an "OK" button which gets the values and then destroys the window. Another way would be to put a trace on variables associated with each entry, and save the values immediately as they are edited.
Which method you choose depends on what behavior you want when the user clicks the window control to close the window (eg: the red circle on OSX, the [x] button on windows, etc). Do you want to return what they had input, or do you treat that as a cancel action and return nothing?
Here's a simple example using an OK button. This example assumes that you aren't already running a GUI, and that this is to be run as part of a non-GUI application.
import tkinter as tk
class Dialog(object):
def show(self, num_fields):
self.num_fields = num_fields
self.root = tk.Tk()
self.entries = []
for i in range(num_fields):
entry = tk.Entry(self.root)
entry.pack(fill="x")
self.entries.append(entry)
ok = tk.Button(self.root, text="OK", command=self.ok)
ok.pack(side="bottom", anchor="e", pady=(10,0), padx=10)
# wait for the window to be destroyed, then
# return the values. If the user clicks the OK button
# the values will be set; if they cancel the dialog
# this will return None.
self.values = None
self.root.wait_window()
return self.values
def ok(self):
# save all the values, then destroy the window
self.values = []
for i in range(self.num_fields):
self.values.append(self.entries[i].get())
self.root.destroy()
Assuming you're running a non-gui program, here's an example of how you would use this class:
b = input("Enter: ")
b = int(b)
result = Dialog().show(b)
print("result:", result)

Categories

Resources