RE: verifying the existence of a Toplevel()
I'm setting a 15-second time limit for the user as they choose whether they want a random vowel or a random consonant, 9 times, to form a random list of letters. (I'm using a Toplevel containing a GUI timer which I got from someone else's answer).
If they manage it on time, a similar timer appears on the ORIGINAL window, replacing the vowel/consonant buttons (which I have destroyed at this point), and the old SEPARATE-window Toplevel timer is destroyed. However, I want to make the second countdown, since it HAS to start at 30 seconds, start at the right time, rather than ticking away in the background (behind the VOWEL/CONSONANT buttons) during which time the user is still selecting letters.
If none of that made any sense, then this is a basic outline of the code that doesn't work.
# I tried to test if the the Toplevel timer had been destroyed (which happens as soon as the
# user has finished with the 9 letters). If so, I could then start the NEW 30-second timer.
import tkinter as tk
root = tk.Tk()
test = tk.Label()
# above: later, will test if a Label() widget counts as a 'child'. If it was the case
# that only Toplevels counted as 'children', then I could have used the 'root.winfo_children'
# command to test if the Toplevel() timer had been destroyed, in which case I can start a new
# 30-second timer on the original window.
test.pack()
extraWindow = tk.Toplevel(root)
extraWindow.destroy() # for below, to TRY and test whether the Toplevel object is destroyed
if not root.winfo_children():
print("N0") # doesn't happen, because test label is also a 'child'
# IMAGINE that this is where I set off the NEW 30-second timer
root.mainloop()
Unfortunately, I have a label on the original window, displaying the list of letters as it develops in the fist 15 seconds, and while the user is coming up with a real word from as many of those letters as possible in the new, 30 seconds. I cannot use winfo_children. Is there something I can do to the effect of winfo_Toplevel?
(EDIT: yes there is; I finally did my homework and found something really obvious that I had missed, so, unfortunately, I answered my own question)
Whoops I just found an answer to my own question.
You can check whether a top level exists using 'tkinter.Toplevel.winfo_exists(my_toplevel_name)'.
If you put this in a print statement, it returns 1 if it does exist, and 0 if it does not.
Related
I have a code snippet. Entry widget creates itself with text, then waits seconds then destroys itself.
entry_var_temporary = tk.StringVar()
entry_var_temporary.set(varsoundTitle_usernameHeroContainer)
entry_shtname_temp=tk.Entry(canvas2,width=30,textvariable=entry_var_temporary)
entry_shtname_temp.pack()
entry_shtname_temp.focus_set()
root.update()
time.sleep(10)
entry_shtname_temp.destroy()
root.update()
I have put 10 seconds to wait, and let user to modify the text, if it wants so. But as I see, time.sleep does not let the entry widget to be modified.
How can I get around this problem?
With wait.window() I realized I can edit text inside widget, but my problem is, it is not compulsory. So, if user doesn't put any text on it, it then after 10 sec needs to be destroyed
This seems to be an extension of a previous question you asked. You only need to add one more line of code to that example in order to automatically delete the entry widget after 10 seconds.
In the following example, notice how I use after to destroy the window in 10 seconds. This call must be done before waiting for the window. This is the example I gave in your previous question, with just that one new statement added:
entry_var = tk.StringVar()
new_sheetname_entryBox=tk.Entry(canvas2,width=30, textvariable=entry_var)
new_sheetname_entryBox.pack()
new_sheetname_entryBox.bind("<Return>", lambda event: new_sheetname_entryBox.destroy())
new_sheetname_entryBox.focus_set()
# wait for the entry widget to be deleted, but automatically
# delete it after 10 seconds if the user doesn't respond.
root.after(10000, new_sheetname_entryBox.destroy)
new_sheetname_entryBox.wait_window()
Use the after method with the destroy method as the callback.
The main reason to use root.after vs time.sleep is the time.sleep stops the thread causing the code to completely stop compared to root.after which is thread-safe because it is implemented in terms of call.
Your widget will destroy itself automatically after a set amount of time.
for example:
import tkinter as tk
root = tk.Tk()
entry_var_temporary = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_var_temporary)
entry.pack()
root.after(10000, entry.destroy) # 10000 milliseconds
root.mainloop()
To understand my question kindly follow the paragraphs written below:
What code does the mainloop processes infinitely? Like does it read the code of the entire program again and again?
consider the code:
from tkinter import *
window = Tk()
print("lol")
print("Hello World")
window.mainloop()
the output didn't print "Hello World" or "lol" infinite number of times, so the mainloop() doesn't loop the code of the current module.
Now consider this code:
from tkinter import *
print("lol")
window = Tk()
print("Hello World")
while True:
window.update()
Now, even this code executes the same output, so now we can consider the mainloop() loops the code "window.update()" infite number of times, but more efficiently(somehow).
Now the first question arises what does the window.update() function do to update the values in the GUI, does it re-read the code from top to bottom again, or how does the update function update the GUI widget vaules.
The second question is :
I read this article
"Mainloop in Python Tkinter is an infinite loop of the application window which runs forever so that we can see the still screen.
The application window is like a frame that keeps on destroying every microsecond but the main loop keeps on creating a new updated window.
This process of destroying old window screens and creating a new one happens so fast that human eyes don’t even realize it.
Since the process runs infinite time that is why we are able to see the application in front of us and when we close the window then the loop terminates or exits."
Now if this is true then to recreate an updated window the root.mainloop() must read the entire root GUI code again and again entirely or is there another explanation to it.
I have been trying to understand this for the past 6hrs and I have visited every site and I cannot find the solution for the life of me.
Regards,
Rashik
What code does the mainloop processes infinitely? Like does it read the code of the entire program again and again?
No.
Via this function, it calls this C code which has the embedded Tcl interpreter process one event, or wait for Tkinter_busywaitinterval before trying to process another event
Now, even this code executes the same output, so now we can consider the mainloop() loops the code "window.update()" infite number of times, but more efficiently(somehow).
window.update() calls TCL update, which is described to
[...] bring the application “up to date” by entering the event loop repeatedly until all pending events (including idle callbacks) have been processed.
Your infinite loop doesn't have a sleep, so it's spinning your CPU as hard as possible to do practically nothing.
[...] Does it re-read the code from top to bottom again, or how does the update function update the GUI widget vaules.
It certainly doesn't re-read your code. It processes any pending widget updates, which may have happened by running e.g. window.text("...") in e.g. a click callback or an .after() timeout, etc.
I read this article [...]
That article seems wrong and/or at least over-simplifies things.
This simple example clock should clarify how things work:
import time
import tkinter as tk
root = tk.Tk()
text = tk.Label(root)
text.pack()
def tick():
text["text"] = time.ctime() # update `text` widget's content
root.after(1000, tick) # schedule for this function to be called after 1 second
if __name__ == '__main__':
tick() # call the `tick` function once before entering main loop
root.mainloop()
I would like to understand why this code:
import time
for i in range(1,11):
print(i)
time.sleep(1)
shows (as it should!) numbers from 1 to 10, each every 1 second, while this code:
from tkinter import *
import time
root = Tk()
for i in range(1,11):
Label(root, text = i).grid(row=0, column=i-1, padx=5, pady =5)
time.sleep(1)
root.mainloop()
waits for 10 seconds, and then displays a window with the 10 numbers (instead of adding them one by one).
I am aware this is a silly question, but I really can't understand! Many Thanks! Alessandro
Most GUI's work differently to what you expect.
They work in an asynchronous way, which means, that you setup your windows and start an event loop.
This event loop will display all widgets, labels, etc, that you set up before calling the event loop and wait for any events (GUI events like mouse or keyboard events, timer events and perhaps network events).
When any event is encountered code associated to that event will be called and this code can request to change the GUI (show or hide elements, change labels or attributes of graphical widgets) However the change to the GUI will only be performed when you give control back to the event loop (when the code handling an event finished)
In your given code you change a label in a for loop with sleep statements, but only after the for loop is finished your main loop is being called and this is the moment, where the final state of your GUI will be displayed.
So what you encounter is a know issue for almost all GUI / asynhronous kind of applications.
You have to rewrite your code such, that you start a timer event, and when the timer event fires a function will set a label and increase the counter by 1. And if the counter is not 11 it will restart another timer
This is because the time.sleep function is before the root.mainloop function.
root.mainloop is what causes the window to appear on-screen and start doing things. Instead, I'd recommend using window.after, as that tells the window to run a function after some time when it's on-screen.
Here's an example of a modification you could make (it's not that good but it works):
from tkinter import *
import time
root = Tk()
progress = 0
end = 10
def update_progress():
global progress
progress += 1
Label(root, text = progress).grid(row=0, column=progress-1, padx=5, pady =5)
if progress < end: root.after(1000,update_progress) # Tell the window to call this function in 1000ms (1 second)
root.after(0,update_progress) # Tell the window to run the update_progress function 0ms after now.
root.mainloop()
I'd recommend looking at gelonida's answer for an explanation of why your original code didn't work, and what you need to keep in mind when programming with GUIs in the future.
Using Python 3.5. I have a tkinter form. The user clicks a button to import many files into a Listbox. Another button loops thru the files and reads and extracts data from them.
I have a Label on the form that indicates the status of the loop. The status, for the most part, works as expected except that extra characters are added on the end. I'm not sure where the characters come from. I also print() the same content as the Label back to the screen and the print() displays exactly what it should.
My question is why is my Label not displaying the correct string?
My code, greatly shortened:
class tk_new_db:
def __init__(self, master):
self.master = master # sets 'root' to the instance variable 'master'
self.var_text_2 = StringVar()
self.var_text_2.set('STATUS: Active')
self.label_6 = Label(master, textvariable=self.var_text_2,
font=self.font_10)
self.label_6.grid(row=15, sticky=W, padx=15)
def execute_main(self): # extract data from files
file_num = 0
nf = len(self.listbox_1.get(0, END))
for fr in li:
file_num += 1
print('STATUS: Extracting Loads from File '
'{} in {}'.format(file_num, nf))
self.var_text_2.set('STATUS: Extracting Loads from File '
'{} in {}'.format(file_num, nf))
self.master.update_idletasks()
The print() is writing the following:
STATUS: Extracting Loads from File 1 in 5
The Label is writing the following:
STATUS: Extracting Loads from File 1 in 5 nce...
It always adds ' nce...' on the Form.
EDIT:
I do use self.var_text_2 earlier in the program. The ' nce...' looks like it is a fragment of the previous string. I've since tried resetting the variable in two different ways, but I'm still getting the same result.
self.var_text_2.set('STATUS: Checking .F06 Files for Convergence...')
self.master.update_idletasks()
self.var_text_2.__del__()
self.var_text_2.set('STATUS: Checking .F06 Files for Convergence...')
self.master.update_idletasks()
self.var_text_2.set('')
How do you properly delete the StringVar() for reuse?
The only explanation I can think of is that you're stacking labels on top of each other in the same row and column, so when you add a long string that ends in "nce..." and later update the screen by adding a shorter label in the same grid cell, the trailing text of the longer label underneath is showing through.
The reasons I draw this conclusion are:
this is a common mistake I've seen several times
there are no bugs in tkinter that would cause this
there is nothing in the code that you've shown that could possibly add "nce..." to the end of the string
the fact that you use sticky="w" rather than sticky="ew", which will cause a shorter label to not completely overlay a longer label if placed in the same row and column
I think I can explain. Root.mainloop repeatedly calls root.update. Root.update executes tasks on the 'main' event queue. In brief, it only executes idletasks when the event queue is empty after checking for file events (on non-Windows), window-gui events, and timer events. Idletasks, which are not well documented but seem to primarily be screen update tasks, are never added to the main event queue. It is therefore possible that idletasks will remain undone indefinitely. Root.update_idletasks exists to force execution of idletasks anyway.
When mainloop is running, calling update within a task run by update is usually unnecessary and possibly worse. It is something only for adventurous experts. Hence the warnings you have read (which assume that you are running mainloop).
When mainloop is not running and update is not being called repeatedly, either because you never called mainloop or because you block it with a long running loop, then you likely must call update yourself. What you seem to have discovered is that part of completely handling a StringVar update is a main event and not just an idletask.
I sympathize with your desire to do repeated tasks with a for loop, but doing so currently means taking responsibility for event handling yourself. I hope in the future that it will be possible to have 'async for' (new in 3.5) interoperate with Tk.mainloop, but it is not as easy as I hoped.
In the meanwhile, you could put the body of the for loop into a callback and loop with root.after.
I'm writing a 'wizard' type Python Tkinter GUI that collects information from the user and then performs several actions based on the user's entries: file copying, DB updates, etc. The processing normally takes 30-60 seconds and during that time, I want to:
Provide the user with text updates on the activity and progress
Prevent the user from closing the app until it's finished what it's doing
I started on the route of having the text updates appear in a child window that's configured to be trainsient and using wait_window to pause the main loop until the activities are done. This worked fine for other custom dialog boxes I created which have OK/cancel buttons that call the window's destroy method. The basic approach is:
def myCustomDialog(parent,*args):
winCDLG = _cdlgWin(parent,*args)
winCDLG.showWin()
winCDLG.dlgWin.focus_set()
winCDLG.dlgWin.grab_set()
winCDLG.dlgWin.transient(parent)
winCDLG.dlgWin.wait_window(winCDLG.dlgWin)
return winCDLG.userResponse
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.userResponse = ''
def showWin(self):
#Tkinter widgets and geometry defined here
def _btnOKClick(self):
#self.userResponse assigned from user entry/entries on dialog
self.dlgWin.destroy()
def _btnCancelClick(self):
self.dlgWin.destroy()
However this approach isn't working for the new monitor-and-update dialog I want to create.
First, because there's no user-initiated action to trigger the copy/update activities and then the destroy, I have to put them either in showWin, or in another method. I've tried both ways but I'm stuck between a race condition (the code completes the copy/update stuff but then tries to destroy the window before it's there), and never executing the copy/update stuff in the first place because it hits the wait_window before I can activate the other method.
If I could figure out a way past that, then the secondary problem (preventing the user from closing the child window before the work's done) is covered by the answers below.
So... is there any kind of bandaid I could apply to make this approach work the way I want? Or do I need to just scrap this because it can't work? (And if it's the latter, is there any way I can accomplish the original goal?)
self.dlgWin.overrideredirect(1) will remove all of the buttons (make a borderless window). Is that what you're looking for?
As far as I know, window control buttons are implemented by the window manager, so I think it is not possible to just remove one of them with Tkinter (I am not 100% sure though). The common solution for this problem is to set a callback to the protocol WM_DELETE_WINDOW and use it to control the behaviour of the window:
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.dlgWin.protocol('WM_DELETE_WINDOW', self.close)
self.userResponse = ''
def close(self):
tkMessageBox.showwarning('Warning!',
'The pending action has not finished yet')
# ...