tkinter: KeyboardInterrupt taking a while - python

Using Tkinter with Python on Linux, I'm trying to make Ctrl+C stop execution by using the KeyboardInterrupt Exception, but when I press it nothing happens for a while. Eventually it "takes" and exits. Example program:
import sys
from Tkinter import *
try:
root = Tk()
root.mainloop()
except:
print("you pressed control c")
sys.exit(0)
How can the program react quicker?

That is a little problematic because, in a general way, after you invoke the mainloop method you are relying on Tcl to handle events. Since your application is doing nothing, there is no reason for Tcl to react to anything, although it will eventually handle other events (as you noticed, this may take some time). One way to circumvent this is to make Tcl/Tk do something, scheduling artificial events as in:
from Tkinter import Tk
def check():
root.after(50, check) # 50 stands for 50 ms.
root = Tk()
root.after(50, check)
root.mainloop()

According to Guido van Rossum, this is because you are stuck in the Tcl/Tk main loop, while the signal handlers are only handled by the Python interpreter.
You can work around the problem, by binding Ctrl-c to a callback function:
import sys
import Tkinter as tk
def quit(event):
print "you pressed control c"
root.quit()
root = tk.Tk()
root.bind('<Control-c>', quit)
root.mainloop()

Related

Tkinter mainloop() not quitting after closing window

This is NOT a duplicate of Python tkinter mainloop not quitting on closing the window
I have an app that builds on tkinter. I observed at sometimes after I close the window using the X button, the code will not execute past the mainloop() line. This happens completely randomly about 10% of chance. Rest of the time, it works like a charm. I would like to ask if there are any way to force it. As I said, the code blocks on the mainloop() line, thus calling sys.exit() after it does not help.
I am using Python 3.9.8.
This is not 100% reproducible, but here is something that might trigger the problem:
from tkinter import *
root = Tk()
Label(root, 'hi').pack()
mainloop()
print('exited')
My first thought is to use root.mainloop() instead of tkinter.mainloop(). This makes a difference if you are using several windows.
That said, I did see this a long time ago on some old OSes. Never figured out the reason, so I just wrote my own quit function, like this:
import tkinter as tk
def _quit():
root.quit()
root.destroy()
root = tk.Tk()
root.protocol("WM_DELETE_WINDOW", _quit)
tk.Label(root, 'hi').pack()
root.mainloop()
print('exited')

Tkinter mainloop in background

I want to run the tkinter mainloop in the background because I´m following the MVC Pattern, so the Controller gets a Interfaceobject to handle all cares. So I tried to thread it. RuntimeError: Calling Tcl from different apartment occures. Saw no further answers on this topic.
Does anyone have a clue what to do?
Thanks, Max
I don´t have the solution you want for this problem. Maybe this question is can show you what happens.
Anyway you can try by defining all tkinter objects inside the function like this:
from tkinter import Tk
import threading
def mainloop():
root = Tk()
#Your tkinter objects goes here
root.mainloop()
t = threading.Thread(target=mainloop)
t.start()
or you can run the mainloop without threading and thread the Controller:
from tkinter import Tk
import threading
root = Tk()
root.mainloop()
def controler():
while True:
pass
t = threading.Thread(target=controler)
t.start()
thanks #Tomás Gomez Pizarro for one solution. I myself came up with a solution. My Controller etc are set up with a reference(Object) to my Gui. the Gui itself, so the mainloop function is not called until everything is ready. So in Runtime every GuiEvent is passed to the Controller via a thread so that the main loop is not interferred with.
Regards, Max

Tkinter tkMessageBox disables Tkinter key bindings

Here's a very simple example:
from Tkinter import *
import tkMessageBox
def quit(event):
exit()
root = Tk()
root.bind("<Escape>", quit)
#tkMessageBox.showinfo("title", "message")
root.mainloop()
If I run the code exactly as it is, the program will terminate when Esc is hit. Now, if I un-comment the tkMessageBox line, the binding is "lost" after closing the message box, i.e. pressing Esc won't do anything anymore. This is happening in Python 2.7. Can you please verify if this is happening also to you? And let me know about your Python version.
Here is a way to "by-pass" the problem. It's a different approach, but it might help:
from Tkinter import *
import tkMessageBox
def msg_test():
tkMessageBox.showinfo("title", "message")
def quit(event):
exit()
root = Tk()
root.bind("<Escape>", quit)
btn = Button(root, text="Check", command=msg_test); btn.pack()
root.mainloop()
Using tkMessageBox via a button click, doesn't affect key binding, i.e. pressing Esc continues to work.
If I understand the problem, you get the bad behavior if you call tkMessageBox.showInfo() before calling mainloop. If that is so, I think this is a known bug in tkinter on windows.
The solution is simple: don't do that. If you need a dialog to show at the very start of your program, use after to schedule it to appear after mainloop has started, or call update before displaying the dialog.
For example:
root = Tk()
root.after_idle(msg_test)
root.mainloop()
The original bug was reported quite some time ago, and the tk bug database has moved once or twice so I'm having a hard time finding a link to the original issue. Here's one issue from 2000/2001 that mentions it: https://core.tcl.tk/tk/tktview?name=220431ffff (see the comments at the very bottom of the bug report).
The report claims it was fixed, but maybe it has shown up again, or maybe your version of tkinter is old enough to still have the bug.

close a window and continue execution in the other one?

I'm really lost...I open a window with two buttons, and when you click on the button called "REGISTER SOME KEY PRESSES" it runs the script called registerSomeKeyPresses.py, BUUUUT once finished I want to close that execution but keep the first window displaying...it's being impossible for me....
Please, i would reaaaally appreciate any help...
Thanks!
#!/usr/bin/env python
from Tkinter import *
import threading
v0 = Tk()
def finishApplication(): v0.destroy()
def registerSomeKeyPresses():
t = threading.Thread(target=execfile("registerSomeKeyPresses.py"))
t.start()
def waitAndRun(f): v0.after(200, f)
b1=Button(v0,text="TERMINAR APLICACION",command=lambda: finishApplication()).pack()
button_keyPresses=Button(v0,text="REGISTER SOME KEY PRESSES",command=lambda: waitAndRun(registerSomeKeyPresses())).pack()
v0.mainloop()
================ registerSomeKeyPresses.py ===========================
Do several things and last command:
io.quit()
When you destroy the instance of Tk, your programm will (and should) exit. If you want to create and destroy windows, create and destroy an instance of Toplevel while keeping the main window active. If you don't want to see the main window you can hide it.
Also, tkinter and threads don't mix very well. You cannot call any methods on any widgets from another thread. I've heard other people say you can call event_generate from another thread, but I think that's the only tkinter function you can call from another thread.
Edit 1
A second try as a response to your comment:
from Tkinter import *
from subprocess import call
import sys
t = Tk()
def click():
t.iconify()
try:
call([sys.executable, 'script.py'])
finally:
t.deiconify() # if it should close do t.quit() and t.destroy()
b = Button(t, command= click)
b.pack()
t.mainloop()
Old Version
What does that do?
================ registerSomeKeyPresses.py ===========================
v0.quit()
v0.destroy()
io.mainloop()
An other error is:
threading.Thread(target=execfile, args = ("registerSomeKeyPresses.py",))
if you really neeed a thread.
Do never mix tkinter mainloop things with threads. Threads can use event_generate - thats safe.

How to end a Tkinter application which hasn't any window shown

I've this simple script and I would love to understand why it's ignoring Ctrl-C.
from Tkinter import *
def main():
root = Tk().withdraw()
mainloop()
if __name__ == "__main__":
main()
I've found this other question on SO (tkinter keyboard interrupt isn't handled until tkinter frame is raised) which is basically the same. Unfortunately, the answer is not clear to me. How should I use the after() function in order to catch the signal?
Moreover, I need Tkinter only to use tkSnack and I don't want any windows to appear. So, adding a button to call root.quit() it's not a possibility.
This seems to work for me if I shift focus back to the command window before typing ctrl-C.
from Tkinter import *
def dummy(root):
root.after(1000, dummy, root)
print '',
def main():
root = Tk()
root.withdraw()
dummy(root)
mainloop()
if __name__ == "__main__":
main()
You can drive this as a Tk application with a hidden window. Following up on your previous question as well where this is all part of an mp3 player here is a sample.
from Tkinter import *
import sys,tkSnack
def terminate(root):
root.quit()
def main(args = None):
if args is None:
args = sys.argv
root = Tk()
root.withdraw()
tkSnack.initializeSnack(root)
root.after(5000, terminate, root)
track = tkSnack.Sound(file=args[1])
track.play()
root.mainloop()
root.destroy()
return 0
if __name__=='__main__':
sys.exit(main())
In this case we hold onto the root Tk window but withdraw it before it gets a chance to be mapped on screen. We then schedule an event for 5 seconds time to terminate the application which will exit the Tk event loop and we can destroy everything and exit cleanly. This works because the play() command causes the sound track to begin playing and returns immediately. The sound data is handled with the event loop - so not running the event loop will halt its playing.
Without the after event causing early termination the application would eventually use up all the sound data from the file and then hang. The play() method takes a command to be run when it finishes playing so we can have the mainloop exit properly at the right time rather than guess the time and use after events.

Categories