Why i am getting this error with root.update() when i destroy the window:
_tkinter.TclError: invalid command name ".!label"
or
_tkinter.TclError: invalid command name ".!frame.!text"
here is an example code:
from tkinter import *
import random
r=Tk()
a=Label(r)
b=[1,2,3,4,5,6,7]
while(True):
a.configure(text=f'{random.choice(b)}')
a.pack()
r.update()
r.mainloop()
any way to fix this error?
The error is because you are running an infinite loop
After closing the window also loop is try to run the program and try to access the Tk() window, but it has been destroyed.
the solution is break the loop also when you close the window
try:
a.configure(text=f'{random.choice(b)}')
r.update()
except:
break
In above code program will try to access the window, if success it will run the program
Otherwise failed it will execute 'except' condition and break the loop
your full code will be
from tkinter import *
import random
r=Tk()
a=Label(r)
a.pack()
b=[1,2,3,4,5,6,7]
while(True):
try:
a.configure(text=f'{random.choice(b)}')
r.update()
except:
break
r.mainloop()
That, or similar, happens any time you close a tkinter window and there is an uncompleted action waiting to process. Frankly, it can be ignored...
..but, if you really want to get rid of it, you need to manage your ongoing processes and wait for them to finish when closing. You will need to overwrite the destroy() method in a customized version of the Tk class to have that, and you will probably be better off using after() if you want active processes going on with the program. The way you are doing it right now, it never hits the mainloop so some functionality will not work.
Here is a simple fix:
from tkinter import *
import random
class Globals:
stop = False
class MyTk(Tk):
def trueDestroy(self):
Tk.destroy(self)
def destroy(self):
Globals.stop = (self)
def myUpdate():
a.configure(text=f'{random.choice(b)}')
if Globals.stop:
r.trueDestroy()
else:
r.after(1, myUpdate)
r=MyTk()
a=Label(r)
a.pack()
b=[1,2,3,4,5,6,7]
r.after(1, myUpdate)
r.mainloop()
Really, you should make a queue for each process you have running instead of just one 'stop' function, and then only destroy it once they all are stopped, but hopefully you can extrapolate form this.
Related
I am currently coding a program that will do something (e.g count numbers constantly) until something is inputted into a dialog box displayed.
However, whenever I try this, the program freezes when waiting for an input and so does not make any progress in the counting process I am trying to run in the background.
Is there any way to have a timer that continuously runs in the background so that in say 5 minutes, the counter instantly stops and the dialog box disappears? This is a basic skeleton of my code. I used the tkinter dialog box for input and tried to create a timer that will run in the background.
from time import *
from tkinter import *
from tkinter import messagebox
from tkinter import simpledialog
while timer<300:
sleep(1)
timer += 1
ROOT = Tk()
ROOT.withdraw()
USER_INP = simpledialog.askstring(title="Code Required",
prompt="What's the Code?:")
Preferably without external modules but if not that is fine. Thanks in advance :)
This is the code requested
from tkinter import *
from tkinter import simpledialog
root = Tk()
root.withdraw()
def ask():
simpledialog.askstring(title="Code Required",
prompt="What's the Code?:")
## root.after(5000, root.destroy()) #added in the root.after() to try and terminate it after set time
root.after(3000,ask) #triggers ask() after 3000 ms(3 seconds)
root.after(100000, root.destroy()) # tried to wait 10 seconds before it breaks but this doesn't show the dialog box any more
root.mainloop()
Here is a basic code with tkinter that makes the dialogbox pop up after 5 seconds.
from tkinter import *
from tkinter import simpledialog
root = Tk()
root.withdraw()
def ask():
simpledialog.askstring(title="Code Required",
prompt="What's the Code?:")
root.after(5000, root.destroy) #added in the root.after() to try and terminate it after set time
root.after(3000,ask) #triggers ask() after 3000 ms(3 seconds)
#root.after(10000, root.destroy) # tried to wait 10 seconds before it breaks but this doesn't show the dialog box any more
root.mainloop()
Here after() triggers a function after the given time, i.e, 3000 ms(3 sec), so you can adjust the timer, out there too. This is just an example and you can edit this more as you like.
Why use after() and not while and a timer?
This is because a while loop interferes a tkinter mainloop() causing the window to be unresponsive, so it is not recommended to use while or time.sleep(). Instead you could use the built-in after() method by tkinter or threading too.
Here is a bit more on after():
It takes two positional arguments,mainly, ms and func
ms - It is the time(in milliseconds) after which the specified function will be triggered.
func - It is the function to be triggered after the specified ms finises.
WARNING:
Keep in mind that the root window is not destroyed, its just hidden, so as long as the root window is not destroyed, the program keeps on running in the background, so you will have to bring back the window and close it for the task to end. For this reason, ive added root.destroy() there.
Take a look here for a bit more understanding on after()
Hope it cleared your doubts, do let me know if any errors.
Cheers
I'm coding a program in Python 3.7 that draws flowers where the user clicks on the screen, but I can't figure out how to detect if the user presses the X button while the functions are still being executed, and then stop all processes and close the window.
I looked for solutions and tried using this answer about using winfo_toplevel but I couldn't make that work either.
My code looks like this:
import turtle, random
from sys import exit
window = turtle.Screen()
window.setup(1000,500)
#my functions to draw go here, but aren't included since the question is about closing the program
def stop():
turtle.bye()
root.destroy()
exit()
window.onclick(chooseFlower)
window.listen()
canvas = window.getcanvas()
root = canvas.winfo_toplevel()
root.protocol("WM_DELETE_WINDOW", stop)
while not root.protocol("WM_DELETE_WINDOW"):
turtle.mainloop()
I get this bunch of errors:
tkinter.TclError: can't invoke "destroy" command: application has been destroyed
and then
raise Terminator
turtle.Terminator
and then
_tkinter.TclError: invalid command name ".!canvas"
What else can I try?
Your code is destroying the window before you click on the X button. The while loop condition is wrong. I think your code should run without that. Also, turtle.bye() will close the window for you without any error. you do not require the destroy function to do so. Try changing code as per below. It should work.
def stop():
turtle.bye()
and comment out while
root.protocol("WM_DELETE_WINDOW", stop)
# while root.protocol("WM_DELETE_WINDOW"):
turtle.mainloop()
Part of my code is as follows:
def get_songs():
label6.configure(text='Wait')
os.system('/home/norman/my-startups/grabsongs')
label6.configure(text='Done')
The label is not updated at the first .configure() but is at the second one.
Except if I cause a deliberate error immediately after the first one at which point it is updated and then the program terminates.
The system call takes about 2 minutes to complete so it isn't as if there isn't time to display the first one.
I am using Python 2.7.6
Does anyone know why please?
I'm going to guess you're using Tkinter. If so, as #albert just suggested, you'll want to call label.update_idletasks() or label.update() to tell Tkinter to refresh the display.
As a very crude example to reproduce your problem, let's make a program that will:
Wait 1 second
Do something (sleep for 2 seconds) and update the text to "wait"
Display "done" afterwards
For example:
import Tkinter as tk
import time
root = tk.Tk()
label = tk.Label(root, text='Not waiting yet')
label.pack()
def do_stuff():
label.configure(text='Wait')
time.sleep(2)
label.configure(text='Done')
label.after(1000, do_stuff)
tk.mainloop()
Notice that "Wait" will never be displayed.
To fix that, let's call update_idletasks() after initially setting the text:
import Tkinter as tk
import time
root = tk.Tk()
label = tk.Label(root, text='Not waiting yet')
label.pack()
def do_stuff():
label.configure(text='Wait')
label.update_idletasks()
time.sleep(2)
label.configure(text='Done')
label.after(1000, do_stuff)
tk.mainloop()
As far as why this happens, it actually is because Tkinter doesn't have time to update the label.
Calling configure doesn't automatically force a refresh of the display, it just queues one the next time things are idle. Because you immediately call something that will halt execution of the mainloop (calling an executable and forcing python to halt until it finishes), Tkinter never gets a chance to process the changes to the label.
Notice that while the gui displays "Wait" (while your process/sleep is running) it won't respond to resizing, etc. Python has halted execution until the other process finishes running.
To get around this, consider using subprocess.Popen (or something similar) instead of os.system. You'll then need to perodically poll the returned pipe to see if the subprocess has finished.
As an example (I'm also moving this into a class to keep the scoping from getting excessively confusing):
import Tkinter as tk
import subprocess
class Application(object):
def __init__(self, parent):
self.parent = parent
self.label = tk.Label(parent, text='Not waiting yet')
self.label.pack()
self.parent.after(1000, self.do_stuff)
def do_stuff(self):
self.label.configure(text='Wait')
self._pipe = subprocess.Popen(['/bin/sleep', '2'])
self.poll()
def poll(self):
if self._pipe.poll() is None:
self.label.after(100, self.poll)
else:
self.label.configure(text='Done')
root = tk.Tk()
app = Application(root)
tk.mainloop()
The key difference here is that we can resize/move/interact with the window while we're waiting for the external process to finish. Also note that we never needed to call update_idletasks/update, as Tkinter now does have idle time to update the display.
I am trying to run a While Loop in order to constantly do something. At the moment, all it does is crash my program.
Here is my code:
import tkinter
def a():
root = tkinter.Tk()
canvas = tkinter.Canvas(root, width=800, height=600)
while True:
print("test")
a()
It will loop the print statement, however the actual canvas refuses to open.
Are there any viable infinite loops that can work alongside Tkinter?
Extra Information
When I remove the While True statement, the canvas reappears again.
Tkinter hangs unless it can execute its own infinite loop, root.mainloop. Normally, you can't run your own infinite loop parallel to Tkinter's. There are some alternative strategies, however:
Use after
after is a Tkinter method which causes the target function to be run after a certain amount of time. You can cause a function to be called repeatedly by making itself invoke after on itself.
import tkinter
#this gets called every 10 ms
def periodically_called():
print("test")
root.after(10, periodically_called)
root = tkinter.Tk()
root.after(10, periodically_called)
root.mainloop()
There is also root.after_idle, which executes the target function as soon as the system has no more events to process. This may be preferable if you need to loop faster than once per millisecond.
Use threading
The threading module allows you to run two pieces of Python code in parallel. With this method, you can make any two infinite loops run at the same time.
import tkinter
import threading
def test_loop():
while True:
print("test")
thread = threading.Thread(target=test_loop)
#make test_loop terminate when the user exits the window
thread.daemon = True
thread.start()
root = tkinter.Tk()
root.mainloop()
But take caution: invoking Tkinter methods from any thread other than the main one may cause a crash or lead to unusual behavior.
In my Program I want to use forget() on a button. Now if I try that, the program crashes. I know that it has something to do with threading but I couldn't find a soultion yet. Thanks in advance. Here is my examplecode:
import Tkinter as tk
import thread
window = tk.Tk()
def ok():
pass
def voice():
button1.forget()
print("If you see this, it works!")
thread.start_new_thread(voice,())
button1=tk.Button(command=ok, text="PRESS")
button1.pack()
window.mainloop()
You can't access tkinter objects from any thread but the thread that creates the object. In other words, you can't call button1.forget() from a thread and expect it to work reliably.
The generally accepted solution is to have your thread(s) write information to a thread-safe queue, and have your GUI thread poll that queue perioducally, pull an item off, and do whatever that item is requesting.
So I solved this problem simply by using the module mtTkinter , which you can find here :
http://tkinter.unpythonic.net/wiki/mtTkinter To use it you only have to write import mtTkinter as Tkinter at the beginning. After that you can use your Tkinter normally. This module changes nothing in Tkinter, it only makes it thread-friendly.
Tkinter is notorious for the fact that its lack of thread safety means that code you have written can sometimes work, and sometimes cause the entire program to hang with no errors produced, which is a pain.
Luckily, Tkinter does have its own measure for dealing with the problem, so to start a a thread with voice in it, just call voice. However, at theend of voice make sure you have made use of the window.after method to call it again later down the line. For example:
import Tkinter as tk
import thread
window = tk.Tk()
def ok():
pass
def voice():
button1.forget()
print("If you see this, it works!")
window.after(10, voice())
voice()
button1=tk.Button(command=ok, text="PRESS")
button1.pack()
window.mainloop(