How to pause Python while Tkinter window is open? - python

I'm writing a program that sometimes encounters an error. When it does, it pops up a Tkinter dialog asking the user whether to continue. It's a more complicated version of this:
keep_going = False
KeepGoingPrompt(keep_going)
if not keep_going:
return
The prompt sets keep_going to True or leaves it False.
Problem is, the code seems to continue while KeepGoingPrompt is open. I tried storing a reference to the prompt and adding a loop like
while prompt:
time.sleep(1)
but python gets stuck in the loop and freezes.
Is there a better way to do it?
Thanks

You can use the tkMessageBox class to pop up a question dialog that is modal and won't return until the user clicks a button. See the Tkinter book for details.

1) Are you running your code inside IDLE? It might be responsible for making the dialogue non-blocking while it really should be blocking.
2) If running outside IDLE does not help, look for tkinter/dialogue options which specify whether behavior is blocking or non-blocking

Related

How can I run a function without interrupting my tkinter mainloop, but also send information from that function to widgets in my mainloop?

Apologies if this has been asked before, but I couldn't find a clear answer. I'm writing a GUI to control a motor from a raspberry pi, and it's on a touchscreen. I've written some code which opens the built in matchbox keyboard when the Entry widget comes into focus, but this halts my entire tkinter window in the background, meaning that the user cannot see what they are entering appear in the Entry until they have closed the keyboard, and also that the screen often tears and looks horrible when it hangs in the background. Is there anyway to run this command:
def createNumpad(event=none):
os.system('matchbox-keyboard numpad')
so that the tkinter window doesn't freeze while the keypad is open? Thanks!
This is the relevant code from a SO answer changed to fit Tkinter:
import subprocess
def createNumpad(event)
try:
subprocess.Popen(["matchbox-keyboard", "numpad"])
except FileNotFoundError:
pass
def deleteNumpad(event):
subprocess.Popen(["killall","matchbox-keyboard"])
#
entry.bind("<Enter>", createNumpad)
entry.bind("<Leave>", deleteNumpad)

Why is KeyboardInterrupt not working for Python pyautogui script? Alternative way to exit program/loop?

I am trying to complete a simple GUI automation program that merely opens a web page and then clicks on a specific spot on the page every 0.2 seconds until I tell it to stop. I want my code to run and have its loop run infinitely until a keybind I specify breaks the loop (or entire program). I started out with the classic KeyboardInterrupt, which supposedly enables CTRL+C to exit a program. Here is my code:
import webbrowser, pyautogui, time
webbrowser.open('https://example.com/')
print('Press Ctrl-C to quit.')
time.sleep(5)
#pyautogui.moveTo(1061, 881)
try:
while True:
time.sleep(0.2)
pyautogui.click(1061,881)
except KeyboardInterrupt:
print('\nDone.')
Unfortunately, KeyboardInterrupt and using CTRL-C to exit do not seem to work for this script (likely due to the while loop?). This causes the loop to continue to run infinitely without a way to be stopped. So my questions are: why isn't the Keyboard Interrupt working? I've seen similar examples in other scripts. Additionally, if the KeyboardInterrupt doesn't work, is there a way I can code a simple keybind to exit the program/loop?
Use the following code
pyautogui.FAILSAFE = True
Then to stop, move the mouse to the upper-left corner of the screen
I suspect it may have something do to with you having a different active window than the script; when you use webbrowser, open a webpage, and click on it, it moves your active window to the webpage rather than the Python console. So ctrl+c will only produce a KeyboardInterrupt when the console is your active window. Your script may be in fact correct; but your active window is not on Python, so you would have to test it by clicking back into the Python console while the program runs.
To answer your comment: No, I do not know of any other "quick" way to do such a thing.
I'm late, but I can provide a solution which allows you to press CTRL + C to stop the program. You need to install the keyboard module and use keyboard.is_pressed() to catch when you press the keys you want as flag:
import keyboard
# You program...
while True:
time.sleep(0.2)
pyautogui.click(1061,881)
if keyboard.is_pressed("ctrl+c"):
break
You need to be careful though because the program will check if you have pressed the keys only when it executes the if statement. If your program runs for 10 seconds and you place the if at the end, you will only be able to exit every 10 seconds and for a very brief moment.
I also suggest to keep the keys pressed while you wait for the program to catch them to avoid missing the moment.
If you instead need to instantly terminate the program without having to always check if CTRL+C are pressed, you can place it in a process and kill it whenever you want. It's a bit overkill and it's not the recommended way, but if you really need it, here it is.
import pyautogui
import keyboard
import time
from multiprocessing import Process
def execute_program():
"""Long program which you want to interrupt instantly"""
while True:
pyautogui.click()
time.sleep(10)
if __name__ == '__main__':
# The failsafe allows you to move the cursor on the upper left corner of the screen to terminate the program.
# It is STRONGLY RECOMMENDED to keep it True
pyautogui.FAILSAFE = True
# Spawn and start the process
execute_program_process = Process(target=execute_program)
execute_program_process.start()
while True:
if keyboard.is_pressed('ctrl+c'):
execute_program_process.terminate()
break
print('\nDone.')

HALT (python 3) a music file?

The user has limited time to solve a problem. On the program, I run a music file at the same time as a GUI (tkinter) timer, and they both finish when the time is up. Some users may finish EARLY. I want to make the music stop when they close the timer window. 'winsound' does not accept 'stop()', and I am having no success with 'winsound.SND_PURGE'. This is more complicated than I thought. I tried this:
# python 3.5
winsound.PlaySound('The Countdown.wav', winsound.SND_FILENAME)
root = Tk() # IMAGINE this is a box with a number ticking down
root.mainloop()
root.protocol("WM_DELETE_WINDOW", winsound.SND_PURGE) # nothing happens :(
Is there a way of using flags? Perhaps an alternative audio-file player? Anything?
Let me know if you do. Thanks!
winsound.SND_PURGE is just an integer constant, that happens to have the value of 64 (and furthermore, is documented as "not supported on modern Windows platforms"). What were you expecting root.protocol() to do with it? You need to pass a function to be called when the window is closed. It looks like:
lambda: winsound.PlaySound(None, 0)
would be a suitable function for stopping sound playback, although I haven't actually tested it.
I think you're also going to need to add winsound.SND_ASYNC to your original PlaySound call's flags, or your window won't even open until the sound is finished.

Tkinter unresponsive when running input(), worse in IDLE

Hello I found a little issue with using the turtle library and python's IDLE. Observe the following code written in Python 3.5.2:
import turtle
turtle.Turtle()
input("Try moving/resizing the window in IDLE. Press enter than try again.")
Before pressing enter to give your input, the turtle window will be labeled as unresponsive and not allow the user to resize it. That is, if it's ran with IDLE. However, it works perfectly fine when ran through something such as the windows command prompt or PowerShell.
If ran through IDLE, after the user presses enter the window may then be freely moved and resized with no issues. This comes with the drawback that we're no longer inside the script but back to python's shell. Of course, we can still interact with the turtle via the shell but this isn't what I want in my actual application. My main program uses input() to ask the user if they wish to move/rotate and by how much. This remains in an endless loop until the user enters a phrase such as 'quit' to end the program. I will note, even with IDLE the turtle still moves and is drawn correctly. The problem is that the window itself becomes unresponsive. The contents is all correct.
I'm very curious to as of why only IDLE is giving me such behavior... Not only that, is there a way I can keep turtle window from being unresponsive and still make use of input() and the like? Or perhaps there is an alternative I did not think of? My research has lead me to believe this has something to do with TkInter. That would explain why it only works outside of IDLE.
Below is an image that shows my predicament.
Unresponsive turtle window
As you suspected, the primary issue is with tkinter, not specifically with the turtle app that uses tkinter. I first reproduced in IDLE with a bare tk window.
import tkinter as tk
root = tk.Tk()
input('prompt: ')
I then did the above a line at a time in 3.5.2 running interactively in Command Prompt on Win10. The second line displays a bare window, add a entry in TaskManager, and adds a Python icon to the task bar. Clicking back and forth between CP and Tk causes the the corresponding window and icon to get the 'active' appearance. The window border shifts from gray to black, whereas the icon background shifts from black to gray.
While typing the input statement, but before hitting return, the Tk window shows 'not responding' (after a couple of seconds) both on the title bar and in TaskManager. The mouse over the inside of the window becomes the blue busy circle. The window becomes 'active' as described above, and can be moved, but cannot be resized and cannot be closed normally (clicking on [X] brings up the "Not responding, close or wait?" box.
When the statement is Entered, the tk window becomes normally responsive again. As soon as the 'user' enters anything, the tk window becomes unresponsive in the manner described above, until entry is completed with Enter.
If you retry in CP and enter some characters without hitting Enter, do you see the same unresponsiveness?
Differences in IDLE's shell: creating root puts a new entry in TaskManager, but the new window is attached to the IDLE icon. While the input statement is being written, the Tk window remains normally responsive. So at this stage, IDLE is better. When the input() statement is Entered, the Tk window becomes, as you noted, completely unresponsive (cannot move) even before any entry. This part is worse.
I tried one further experiment: running IDLE with the -n ('no subprocess') option.
C:\Users\Terry>python -m idlelib -n
In this mode, there is no problem that I could detect (unlike CP). This was still true when I ran turtle. You can ignore the deprecation warning for now.
Comment 1: IDLE is designed for development and learning, not for production execution. But it can be used for the latter if there is an advantage to doing so. You would just have to check that using -n does not introduce any other problems, or rather, that turtle and your code do not interfere with IDLE when running in the same process.
Comment 2: GUI programs usually do not use input and print. They are usually run without a console to interact with. If input() is used, even in the Windows console, user should not touch the window before responding to a prompt.
Comment 3: GUI programs usually use GUI widgets instead to fetch and display info. Turtle makes this more difficult, but not impossible, as demonstrated by turtledemo. You might be simplify and adapt the code in turtledemo/main.py.

What magic prevents Tkinter programs from blocking in interactive shell?

Note: This is somewhat a follow-up on the question: Tkinter - when do I need to call mainloop?
Usually when using Tkinter, you call Tk.mainloop to run the event loop and ensure that events are properly processed and windows remain interactive without blocking.
When using Tkinter from within an interactive shell, running the main loop does not seem necessary. Take this example:
>>> import tkinter
>>> t = tkinter.Tk()
A window will appear, and it will not block: You can interact with it, drag it around, and close it.
So, something in the interactive shell does seem to recognize that a window was created and runs the event loop in the background.
Now for the interesting thing. Take the example from above again, but then in the next prompt (without closing the window), enter anything—without actually executing it (i.e. don’t press enter). For example:
>>> t = tkinter.Tk()
>>> print('Not pressing enter now.') # not executing this
If you now try to interact with the Tk window, you will see that it completely blocks. So the event loop which we thought would be running in the background stopped while we were entering a command to the interactive shell. If we send the entered command, you will see that the event loop continues and whatever we did during the blocking will continue to process.
So the big question is: What is this magic that happens in the interactive shell? What runs the main loop when we are not doing it explicitly? And why does it need to halt when we enter commands (instead of halting when we execute them)?
Note: The above works like this in the command line interpreter, not IDLE. As for IDLE, I assume that the GUI won’t actually tell the underlying interpreter that something has been entered but just keep the input locally around until it’s being executed.
It's actually not being an interactive interpreter that matters here, but waiting for input on a TTY. You can get the same behavior from a script like this:
import tkinter
t = tkinter.Tk()
input()
(On Windows, you may have to run the script in pythonw.exe instead of python.exe, but otherwise, you don't have to do anything special.)
So, how does it work? Ultimately, the trick comes down to PyOS_InputHook—the same way the readline module works.
If stdin is a TTY, then, each time it tries to fetch a line with input(), various bits of the code module, the built-in REPL, etc., Python calls any installed PyOS_InputHook instead of just reading from stdin.
It's probably easier to understand what readline does: it tries to select on stdin or similar, looping for each new character of input, or every 0.1 seconds, or every signal.
What Tkinter does is similar. It's more complicated because it has to deal with Windows, but on *nix it's doing something pretty similar to readline. Except that it's calling Tcl_DoOneEvent each time through the loop.
And that's the key. Calling Tcl_DoOneEvent repeatedly is exactly the same thing that mainloop does.
(Threads make everything more complicated, of course, but let's assume you haven't created any background threads. In your real code, if you want to create background threads, you'll just have a thread for all the Tkinter stuff that blocks on mainloop anyway, right?)
So, as long as your Python code is spending most of its time blocked on TTY input (as the interactive interpreter usually is), the Tcl interpreter is chugging along and your GUI is responding. If you make the Python interpreter block on something other than TTY input, the Tcl interpreter is not running and the your GUI is not responding.
What if you wanted to do the same thing manually in pure Python code? You'd of need to do that if you want to, e.g., integrate a Tkinter GUI and a select-based network client into a single-threaded app, right?
That's easy: Drive one loop from the other.
You can select with a timeout of 0.02s (the same timeout the default input hook uses), and call t.dooneevent(Tkinter.DONT_WAIT) each time through the loop.
Or, alternatively, you can let Tk drive by calling mainloop, but use after and friends to make sure you call select often enough.

Categories