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.
Related
I'm using Tkinter to show a login dialog and then run my main logic.
I intend for the following snippet to close the window (finish the main loop of tk) after clicking the button and just print indefinitely (it is wrapped in while True is in order for the whole script to continue executing, which simulates a real program logic).
But instead, the following snippet hangs the window and fails to close in macOS Ventura with Python 3.10:
from time import sleep
from tkinter import Tk, Button
def quit():
root.quit()
root = Tk()
Button(root, text="Quit", command=quit).pack()
root.mainloop()
while True:
sleep(1)
print("Running a program logic...")
I've tried to run a functional version of this code (which fails the same) and a threaded version of it (which just crashes since an NSWindow must be created on the main thread).
I just really can't wrap my head around it!
EDIT: Fully working example
EDIT 2: Clarify the intention of this code
EDIT 3: Even more minimal code
Try this:
from Tkinter import *
def quit():
global root
root.quit()
root = Tk()
while True:
Button(root, text="Quit", command=quit).pack()
root.mainloop()
I have a python file that contains:
from tkinter import *
from tkinter.scrolledtext import ScrolledText
import time
root = Tk()
scroll_text = ScrolledText(root)
scroll_text.pack()
def task0():
time.sleep(5) # some code which takes 5 secs
pass
def task1():
# some code which takes a few seconds
pass
def task2():
# some code which takes a few seconds
pass
# ...
for i in range(0, 5):
scroll_text.insert(INSERT, 'program start to do task{0}\n'.format(i))
if i == 0:
task0()
elif i == 1:
task1()
elif i == 2:
task1()
# ...
print('Finish')
root.mainloop()
I expect when program starts immediately see the program start to do task .. text in the tkinter window and after that start executing functions, each lasting a few seconds.
What should I do to make the events occur in the same order as the code I wrote (ie, first show the task number with program start to do task .. to show the user the progress of the program in the tkinter window and then execute functions).
TKinter is not updating the window because the mainloop() is blocked from running by your loop. The solution depends on what actual problem you are trying to solve, which isn't clear from a blank loop. If you're simply trying to create a 5-second delay, use tkinter's after() method to schedule your print() command to run later without blocking the mainloop() until then.
Your window isn't created until root.mainloop() is executed. Your programs first waits then creates the window.
What you can do is, add this line:
root.after(5000,lambda: print('Finish'))
After 5 second(5000 milliseconds), it will call the lambda function, which will print.
In the code below, pressing the space bar twice results in two successive beeps. I want to avoid this and instead disable the key while the first beep is happening. I thought unbinding the space key might work, but it doesn't. It's strange that only two beeps seem to stack up rather than more. I'm guessing maybe the cause of the issue is that winsound.Beep is non-blocking so the rebinding occurs almost instantly.
Any suggestion on how to get this to work please?
import winsound
from tkinter import *
def beep(e):
frame.unbind("<space>")
winsound.Beep(440, 1000)
frame.bind("<space>", beep)
root = Tk()
frame = Frame(root, width=100, height=100)
frame.bind("<space>", beep)
frame.pack()
frame.focus_set()
root.mainloop()
Here is a solution that takes the focus away from the widget, so the binding wont get triggered:
import winsound
from tkinter import *
def beep(event):
dummy.focus_set() #setting focus to dummy
winsound.Beep(440, 1000) #playing it
root.after(1000,frame.focus_set) #setting focus back after playing for 1000 ms
root = Tk()
dummy = Label() #making a dummy widget
dummy.pack()
frame = Frame(root, width=100, height=100)
frame.bind("<space>",beep)
frame.pack()
frame.focus_set()
root.mainloop()
I've commented it to understand better, but this is just a way around and its not that complicated to understand either.
Also keep in mind, in all cases of using winsound, as long as that beep has started and finished playing, the GUI will be unresponsive, that is, GUI will be unresponsive for 1 sec(in your case).
This should fix it however you have to download keyboard module with pip install keyboard :
import winsound
from tkinter import *
import keyboard
from _thread import start_new_thread
def beep():
while True:
if keyboard.is_pressed('space'):
winsound.Beep(440, 1000)
root = Tk()
frame = Frame(root, width=100, height=100)
start_new_thread(beep, ())
frame.pack()
frame.focus_set()
root.mainloop()
First start_new_thread()(syntax is important) makes beep() threaded (runs in background?) and its a while loop so it runs continuously and whenever you press space it will beep and even if you spam space it will still just run one beep. However there is a downside. It will run while script is not terminated so if you focus out it will still beep if you press spacebar
You can use the elapsed time since the last successful keypress to decide if the beep should be produced or not.
maybe like this: I do not have access to winsound, so I am using an os feature to mimic a beep. You can comment this out, and uncomment the calls to winsound
# import winsound
import os
import tkinter as tk
import time
def beep(e, time_limit=1, timer=[0]):
t0 = timer[0]
t1 = time.time()
delta_t = t1 - t0
if delta_t < time_limit:
return
# winsound.Beep(440, 1000)
os.system('say "Beep"')
timer[0] = t1
root = tk.Tk()
frame = tk.Frame(root, width=100, height=100)
frame.bind("<space>", beep)
frame.pack()
frame.focus_set()
root.mainloop()
You can bind back the event via after_idle():
def beep(e):
e.widget.unbind('<space>')
winsound.Beep(440, 1000)
e.widget.after_idle(e.widget.bind, '<space>', beep)
explanation:
The callback passed to after_idle() will be executed when tkinter mainloop is idle, i.e. no pending works/events to be handled. So if the spacebar is pressed many times, the first press triggers beep() in which tkinter unbinds the event, beep and then schedules the rebind. After beep() returns, tkinter keeps handling the pending tasks, i.e. handle the rest spacebar events (but at that moment, no bind is active) and then do the after_idle schedule task which is the rebind.
So I have this program which runs inside a tkinter window. The idea is that when a user finishes using the porgram their scores/info from that session is stored, so their scores should only be stored after they shut the window down.
I am wondering if there is anything which could tell the program when the user presses the close button (from the window, not an in-window widget) so that processes only happen after they close the window.
The mainloop function only ends once the root window is closed, so you can just put your code after that.
from Tkinter import *
root = Tk()
root.mainloop()
print("This message should appear after the window closes.")
I suppose you could also register a protocol handler to catch WM_DELETE_WINDOW events, but that seems like an unnecessary complication.
from Tkinter import *
def x_button_pressed():
print("This message should appear after the window closes.")
root.destroy()
root = Tk()
root.protocol("WM_DELETE_WINDOW", x_button_pressed)
root.mainloop()
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()