0

The code below describes a confirmation popup function with Tk. The goal is that when the message box pops up, concurrently another thread is counting down from 120 seconds. Once the countdown is over, I want the Tk window to automatically shut and continue. My problem is that Tk has issues multithreading, and I have no idea on how to get around this.

def showConfirmationPopup():
    forceClose = False
    def trigger():
        forceClose = True
        root.destroy()
    root = tk.Tk()
    root.withdraw()  # Hide the main window
    root.iconbitmap(default='blank.ico')
    countdown_thread = threading.Thread(target=countdown,args=(root,trigger,))
    countdown_thread.start()
    result = messagebox.askyesno(XXX)
    if forceClose:
        result = True
    if not result:
        XXX
    return result

def countdown(result,close):
    t = 120
    while t > 0:
        t -= 1
        time.sleep(1)
    close()

When the other thread is executed and the countdown finishes you can see the result below.

Exception in thread Thread-1 (countdown):
Traceback (most recent call last):
  File "C:\Users\xxx\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 1038, in _bootstrap_inner
    self.run()
  File "C:\Users\xxx\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\xxx\script.py", line 15, in countdown
    close()
  File "C:\Users\xxx\script.py", line 42, in trigger
    root.destroy()
  File "C:\Users\xxx\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 2368, in destroy
    self.tk.call('destroy', self._w)

Any help is appreciated. :)

Justin
  • 3
  • 3

1 Answers1

0

You can use after() to schedule exiting the app after your desired timeout. I've also set this up to exit when the user picks an option from the dialog - note the use of contextlib's suppress, which prevents tkinter from complaining about quitting an app that's already been destroyed (in the event of a timout)

One minor caveat is that the 'No' case will also execute if the app exits on a timeout, so plan accordingly!

import tkinter as tk
import tkinter.messagebox as tkm
from contextlib import suppress


root = tk.Tk()
root.withdraw()  # minimize the window
TIMEOUT = 120_000  # desired timeout in mS
root.after(TIMEOUT, root.destroy)  # exit on timeout


# suppress the error when we try to quit the app if it's already destroyed
with suppress(tk.TclError):
    # show the dialog window
    if tkm.askyesno('Please choose one', 'This is very important'):
        print('yep')  # do something here
    else:
        print('nope')  # or do something else 
        # this will also happen on timeout if no choice is made
    root.quit()  # exit on user decision (you could also use root.destroy())

You'll also notice the lack of root.mainloop() here - this is done so the application will exit as soon as a decision is made, rather than waiting for the timeout regardless of the user's action.

JRiggles
  • 4,847
  • 1
  • 12
  • 27