How to check if toplevel exists after destroying it in tkinter? - python-3.x

I'm trying to check if a particular Toplevel has been destroyed, which happens after a certain button is pressed, so that I can then do something else in the program (i.e. create a NEW Toplevel).
Supposedly, after the initial toplevel is closed by the user, the output on the shell should be "N". This would indicate that the program understood that the initial toplevel no longer existed, allowing me to carry onto the next stage in that particular if not t1.winfo_exists(): clause (see below).
This output does not happen. Nothing happens on the output. I used 'winfo_exists()' and I cannot find what I have done incorrectly.
from tkinter import *
root = Tk()
t1 = Toplevel(root)
t1.title('REG')
def GetREG():
global e, reg
reg = e.get() # find out the user input
# Destroy the toplevel:
t1.destroy() # after the user presses the SubmitButton
label = Label(t1, text="Enter your REG:")
label.pack()
e = Entry(t1) # for the user to input their REG
e.pack()
SubmitButton = Button(t1,text='Submit',command=GetREG) # button to submit entry
SubmitButton.pack(side='bottom')
if not t1.winfo_exists(): # TRYING TO CHECK when the does not exist
# supposedly, this should occur after the SubmitButton is pressed
# which shold allow me to then carry out the next step in the program
print("No")
root.mainloop()
Could it be that when the user destroys a window, this is not recognized as a non-existent state? It doesn't work either when I 'delete' the t1 toplevel with the cross, or when it gets deleted via the SubmitButton (with t1.destroy() in GetREG()).
Any suggestions?

At the time you check for t1.winfo_exists(), the window still exists because you're calling that function about a millisecond after it has been created. How is tkinter supposed to know that you want that if statement to wait for the window to be destroyed?
If you want to wait for it to be destroyed before executing more code you can use the method wait_window, which is like mainloop in that it processes events until the window is destroyed.
from tkinter import *
root = Tk()
t1 = Toplevel(root)
...
root.wait_window(t1)
# there is now no need to check, since it's not possible to get
# here until the window has been destroyed.
root.mainloop()

Related

The second time I use a ttk button (in root) to create a toplevel window, the toplevel window cannot be destroyed

Basically, I have a button in root which creates a Toplevel. Then in Toplevel, I also have a ttk.Button, which when clicked will destroy the Toplevel. I am using .destroy(), NOT .quit() or any other wrong commands, which is evident as the first time I create the Toplevel with my root button, my Toplevel button WILL destroy the Toplevel.
However, the second time I click the root button to re-create the Toplevel (in other words, to re-create the Toplevel), the Toplevel will successfully be recreated, but the Toplevel button will not destroy the Toplevel, and I can't understand why. Here is my code (I left in all the additional widgets because when I don't include them, the destroying of the Toplevel works just fine):
from tkinter import *
from tkinter import ttk
class App():
def __init__(self,root):
root.title('My Flashcards')
root.resizable(False,False)
button_create = ttk.Button(root,text='Create New',command=self.create_new).grid(column=1,row=2)
def create_new(self):
self.create_branch = Toplevel()
self.create_branch.resizable(False,False)
self.create_branch.title('Create new flashcards')
ttk.Label(self.create_branch,text='CREATE A NEW FLASHCARD HERE:').grid(column=1,row=1,pady=(10,10),padx=(10,10))
ttk.Label(self.create_branch,text='Type the question:').grid(column=1,row=4,padx=(10,10),pady=(10,10))
self.create_question = Entry(self.create_branch,width=70)
self.create_question.grid(column=1,row=5,padx=(10,10))
ttk.Label(self.create_branch,text='Type the answer:').grid(column=1,row=6,padx=(10,10),pady=(10,10))
self.create_answer = Text(self.create_branch)
self.create_answer.grid(column=1,row=7,padx=(10,10))
self.submit_new_flashcard = ttk.Button(self.create_branch,text='Submit',command=self.submit_new_flashcard)
self.submit_new_flashcard.grid(column=1,row=8,pady=(10,10))
def submit_new_flashcard(self):
#Do some things here with self.create_answer.get() and self.create_question.get()
self.create_branch.destroy()
if __name__ == "__main__":
root = Tk()
App(root)
root.mainloop()
You have a method submit_new_flashcard. When you create the button and assign command=self.submit_new_flashcard the button is created and the command property bound to the method. Then you assign the self.submit_new_flashcard field to be the result of creating that button. When you destroy the button, the reference name is still held in the variable. So in creating the second form you create a button that tries to call the original button name as a function which doesn't do anything but raises a background error.
In short, improved naming and avoiding reuse of the same names in the same scope. eg:
self.submitButton = ttk.Button(..., command=self.submit)
would avoid the issue.

How to disable tkinter listbox during a function

I have a GUI created in Python 3.x using tkinter from Visual Studio 2017 Community. A top widget is created and within it a frame and listbox are added. When the user clicks on an entry in the listbox the index of the selection is passed to twoSecondFunction() which takes around 2 seconds to complete.
How can I stop the user from making another selection during this time?
I have tried using the line myListbox.config(state=tkinter.DISABLED) to disable the listbox when a selection has been made and only enabling the listbox again once the twoSecondFunction() has completed.
When I run the code from Visual Studio myListbox click events are handled by myListboxSelection and 'twoSecondFunction()' and print() are called. The window appears to be unresponsive for the duration of the myListboxSelection().
if the user clicks on another entry before
Function finished
has been output myListboxSelection() is called another time.
I can queue over 10 commands by clicking quickly before "Function finished" is output. The GUI appears unresponsive for the duration of all queued events and only responds to other commands after executing all the queued myListboxSelection() calls.
I have tried removing the line myListbox.config(state=tkinter.NORMAL) and this then registers only one click for the duration of the program, so myListbox.config(state=tkinter.DISABLED) is working as it should when it is called. myListbox also greys out.
I have also added extra print() lines throughout the program to ensure all operations happen in the expected order which they do.
It seems as though the logic executes a lot more quickly than the GUI itself responds, so myListbox becomes enabled far more quickly than the GUI responds. I do not see myListbox grey out during the execution of 'twoSecondFunction()'.
Is it because the myListbox.config(state=tkinter.DISABLED) only takes effect after the event handler completes its execution? I.e. myListbox.config(state=tkinter.DISABLED) never takes effect because myListbox.config(state=tkinter.NORMAL) is set before myListbox is actually disabled?
import tkinter #For the GUI tools
#Event handler for mouse click of myListbox
def myListboxSelection(event):
myListbox.config(state=tkinter.DISABLED) #Disable myListbox
myListboxIndex = int(myListbox.curselection()[0]) #Get selection index
twoSecondFunction(myListboxIndex) #Call function that takes 2 seconds
print("Function finished") #Output to console on completion
myListbox.config(state=tkinter.NORMAL) #Enable Listbox
#Create GUI
GUITopWidget = tkinter.Tk(screenName = "myListboxGUI") #Create Top Level Widget
myFrame = tkinter.Frame(GUITopWidget, name = "myFrame") #Create frame
myFrame.pack() #Pass to Geometry Manager
myListbox = tkinter.Listbox(authoritiesListFrame) #Create myListbox
myListbox.bind('<<ListboxSelect>>', myListboxSelection) #Bind mouse click event
populateListbox(myListbox) #Add entries to the listbox
myListbox.pack() #Pass to Geometry Manager
#Run the GUI loop
GUITopWidget.mainloop()
It appears that if you have an event handler and use mainloop() it does indeed stop any logic from executing during the callback. Instead define a loop which can handle the event calls, other logic and call update() on the GUI manually:
import tkinter #For the GUI tools
rootWindowClosed = False
requestQueued = False #True request is queued
myListboxIndex= -1 #Set to -1 between requests
#Event handler for mouse click of myListbox
def myListboxSelection(event):
myListbox.config(state=tkinter.DISABLED) #Disable myListbox
myListboxIndex = int(myListbox.curselection()[0]) #Get selection index
#On closing of root window
def closeGUIHandler():
#Set the rootWindowClosed flag to True
global rootWindowClosed
rootWindowClosed = True
#Destroy GUI Root
GUITopWidget.destroy()
#Create GUI
GUITopWidget = tkinter.Tk(screenName = "myListboxGUI") #Create Top Level Widget
GUITopWidget.protocol("WM_DELETE_WINDOW", closeGUIHandler) #Set delete window protocol
myFrame = tkinter.Frame(GUITopWidget, name = "myFrame") #Create frame
myFrame.pack() #Pass to Geometry Manager
myListbox = tkinter.Listbox(authoritiesListFrame) #Create myListbox
myListbox.bind('<<ListboxSelect>>', myListboxSelection) #Bind mouse click event
populateListbox(myListbox) #Add entries to the listbox
myListbox.pack() #Pass to Geometry Manager
#Run the GUI loop
while rootWindowClosed == False:
#If requestQueued then execute queued request
if requestQueued == True:
twoSecondFunction(myListboxIndex) #Call function that takes 2 seconds
myListbox.config(state=tkinter.NORMAL) #Enable Listbox
myListboxIndex = -1
#Update GUI window if not closed
time.sleep(0.05) #Sleep for 50 ms to allow event handling before executing code
if rootWindowClosed == False:
GUIRootWidget.update()

Tkinter - How to trace expanding list of variables

What I am trying to do track when any values in a list of StringVar change, even when the list is expanding. Any additions to the list before the trace statement will result in the callback. But any additions afterward, such as when pressing a button, will not cause any callback.
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
add = tk.Button(frame,text='add Entry',command='buttonpressed')
add.grid(row=0)
add.bind('<Button-1>',add_entry)
for i in range(2):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
for i in L:
i.trace('w',lambda *arg:print('Modified'))
root.mainloop()
Modifying the first two Entry's prints out Modified, but any Entry's after the trace is run, such as the ones produced when a button is pressed, will not.
How do I make it so that trace method will run the callback for the entire list of variables even if the list is expanded?
Simple suggestion, change your add_entry function to something like this:
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[len(L)-1].trace('w',lambda *arg:print('Modified'))
Extra suggestions:
This add = tk.Button(frame,text='add Entry',command='buttonpressed') is assigning a string to command option, means it will try to execute that string when button is clicked(which will do nothing). Instead, you can assign your function add_entry to command option and it will call that function when button is clicked and you can avoid binding Mouse Button1 click to your Button(Note: No need to use argument event in function when using like this). Read more here
Python supports negative indexing of List, so you can call L[-1] to retrieve the last element in the list instead of calling L[len(L)-1]).
Once you change your add_entry function as suggested, you can reduce your code to
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry():
global L
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[-1].trace('w',lambda *arg:print('Modified'))
add = tk.Button(frame,text='add Entry',command=add_entry)
add.grid(row=0)
for i in range(2):
add_entry()
root.mainloop()

Python 3.5.1 Tkinter // Entry widget/Frame management issue

So I want my program to be able to reset the password. The problem is that everything works just fine with the first reset process, but if you wanted to reset the password one more time it gets wicked and I can't find a mistake or solution. What the program does the first time and what it should do:
When button3 is pressed show the first frame where you have to enter you current pw. If the enter key on the keyboard is pressed and the pw is right show the second frame. There, set a new pw and by hitting enter show the third frame. Type in the same pw you used in the second frame, the pw set frame. If both pw are the same go back to the title screen.
Second+ time and the PROBLEM: button3 is pressed, it shows the first frame, but the cursor is not in the entry field like it is the first time (entry.focus_set() is in the code), BUT the cursor is in the third entry field (of the last, the pw confirm, frame) without it even being .pack()ed or .raised or anything beyond. If I click the entry of the first frame (where I actually would be at this point) and confirm it shows the second frame like it is supposed to be, but the cursor now is in the entry of the first frame which is not even active anymore because it just .pack_forget(). If I proceed to the third frame the cursor is in the entry of the second frame.
from tkinter import *
root = Tk()
pw_actual = ""
pw_first = ""
def ssc_title():
sc_pw.pack_forget()
sc_pw_second.pack_forget()
sc_title.pack()
def ssc_pw_change():
sc_title.pack_forget()
sc_pw_testing.pack()
def ssc_pw_first():
sc_pw_testing.pack_forget()
sc_pw_first.pack()
def ssc_pw_second():
sc_pw_first.pack_forget()
sc_pw_second.pack()
#-----------------------------------
def ev_pw_testing(event):
pw_proof = en_pw_testing.get()
if pw_proof == pw_actual:
en_pw_testing.delete(0, END)
ssc_pw_first()
else:
en_pw_testing.delete(0, END)
def ev_pw_first(event):
global pw_first
pw_first = en_pw_first.get()
if pw_first == "":
errormessage("Please enter a password")
else:
en_pw_first.delete(0, END)
ssc_pw_second()
def ev_pw_second(event):
global pw_first
pw_second = en_pw_second.get()
if pw_first == pw_second:
write(pw_second)
en_pw_second.delete(0, END)
ssc_title()
else:
errormessage("Passwords don't match")
en_pw_second.delete(0, END)
#------------------
sc_pw_testing = Frame(root, bg=bgclr)
lab_pw_testing = Label(sc_pw_testing, text="Please enter the password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_testing = Entry(sc_pw_testing, show="X")
en_pw_testing.pack()
en_pw_testing.focus_set()
en_pw_testing.bind("<Return>", ev_pw_testing)
sc_pw_first = Frame(root, bg=bgclr)
lab_pw_first = Label(sc_pw_first, text="Set new password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_first = Entry(sc_pw_first, show="X")
en_pw_first.pack()
en_pw_first.focus_set()
en_pw_first.bind("<Return>", ev_pw_first)
sc_pw_second = Frame(root, bg=bgclr)
lab_pw_second = Label(sc_pw_second, text="Confirm password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_second = Entry(sc_pw_second, show="X")
en_pw_second.pack()
en_pw_second.focus_set()
en_pw_second.bind("<Return>", ev_pw_second)
sc_title = Frame(root, bg=bgclr)
bttn3 = Button(sc_title, text="Password reset", bg=bgclr, fg=fontclr, font=font_12, relief=FLAT, command=ssc_pw_change).pack()
#------------
root.mainloop()
(Here I didn't include irrelevant functions etc like the errormessage toplevel funtion which justs create a window or the pickle stuff, because then it'll be too long and the issue does not seem to be laying there.)
Like I was saying - the first time changing the password works just fine. Every other time after the first time the same problem occures every time. The only way to get it to work again is to restart the program. I was trying following solutions:
resetting the global variables before entering the reset process again
resetting the global variables after switching the frame and being used
putting each frame (or page if you will) into a function
putting each frame (or page) into a class following the Switch between two frames in tkinter tutorial
leaving the frame/page switcing functions out and put the .pack()s and .pack_forget()s right in the pw checking (event) functions
instead of packing using a grid
using ("", lambda event: function())
Each solution worked just like the code right now, no changes at all, and the problem stays just as well.
I mean I could work around it, like disable the button so you can only reset the pw one time each session, but there is no reason why it should be like that, or even omit the entry.focus_set(), so you have to click the entry field no matter what and the problem never has a chance to occur (which is seriously annoying to click each entry field before entering characters each time).
I ran out of ideas what I could try to do differently. I feel like missing the forest for the trees. Come on guys, gimme a kick in my butt, what am I missing out here!?

Use return key and a button at the same time in tkinter

I want to write a program for my biology class... I want to integrate the function that you can type something in the Entry bar and then you can use the button or click the return key. I've the problem, that I just can click the button. Everything else don't work. Here is my code (in a simple form):
from tkinter import *
import tkinter as tk
# Main Graphic User Interface
root = Tk()
root.title("Genetic Translator")
root.geometry("300x175")
root.resizable(0,0)
# Solid Label "Information for Input"
s_label2 = Label(root, text = "\nInput Tripplet which decodes for an amino acid:\n")
s_label2.pack()
# Entry Bar
trip = Entry(root)
trip.pack()
# Function for setting focus on entry bar
trip.focus_set()
# Dictionary
output = {"GCX":"Alanine [Ala]"}
# Dict Function Function (Trans: trip -in- AS)
def dict_function1():
global o_screen
o_screen.configure(text=(output.get(trip.get().upper(),"Unknown tripplet!")))
# Bind the Return Key for Input
trip.bind("<Return>", dict_function1)
# Space Label 1
space_label1 = Label(root)
space_label1.pack()
# Button "Confirm"
mainbutton = Button(root, text = "Confirm", command = dict_function1)
mainbutton.pack()
# Space Label 2
space_label2 = Label(root)
space_label2.pack()
# Output Screen
o_screen = Label(root)
o_screen.pack()
# Mainloop function for Interface Options
root.mainloop()
Thank you for helping me.
When you press return key it will send event as argument to dict_function1 and when you click on the button nothing is send.
add argument to dict_function1 with None as default value.
def dict_function1(event=None)
Function assigned to button is called without arguments but assigned by bind is called with argument - event information - so your function have to receive that argument
def dict_function1(event=None): # None for "command="
--
<Return> binded to Entry will work only if Entry is focused, but not when Button is focused. If you bind <Return> to root then <Return> will work in both situations.
You neglected to say what "don't work" means. When I run your code from IDLE, enter 3 letters, and hit return, I get the following
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Programs\Python35\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
TypeError: dict_function1() takes 0 positional arguments but 1 was given
The issue is that when tk calls a 'command', it does not pass any arguments, but when it calls a function bound to an event, it passes an event argument. So add an optional parameter to the function.
def dict_function1(event=None):
It works for me, except for the error message when the Enter key is pressed which you don't provide, so that may or may not be the problem. It is easily fixed, but "everything else don't work" is way too vague to help you with. See "Capturing keyboard events" at http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm You should also include code if the Entry value is not found in the dictionary. Finally, you import Tkinter twice in the first 2 statements in the program. Choose one or the other.
from tkinter import *
# Main Graphic User Interface
root = Tk()
root.title("Genetic Translator")
root.geometry("300x175")
root.resizable(0,0)
# Solid Label "Information for Input"
s_label2 = Label(root, text = "\nInput Tripplet which decodes for an amino acid:\n")
s_label2.pack()
# Entry Bar
trip = Entry(root)
trip.pack()
# Function for setting focus on entry bar
trip.focus_set()
# Dictionary
output = {"GCX":"Alanine [Ala]"}
# Dict Function Function (Trans: trip -in- AS)
def dict_function1(arg=None): ## capture the event from the Return key
##global o_screen
o_screen.configure(text=(output.get(trip.get().upper(),"Unknown tripplet!")))
# Bind the Return Key for Input
trip.bind("<Return>", dict_function1)
# Space Label 1
space_label1 = Label(root)
space_label1.pack()
# Button "Confirm"
mainbutton = Button(root, text = "Confirm", command = dict_function1)
mainbutton.pack()
# Space Label 2
space_label2 = Label(root)
space_label2.pack()
# Output Screen
o_screen = Label(root)
o_screen.pack()
# Mainloop function for Interface Options
root.mainloop()

Resources