0

For some reason the mainloop() is not ending. The final print statement never gets triggered but everything else does. Any idea what is causing this or how to resolve it? It happens even without the threading.

import time
from tkinter.filedialog import askdirectory
from tkinter import *

def threading():
    t1=Thread(target=checkProgress)
    t1.start()

def checkProgress():
    loading_window.geometry = ("500x500")
    text = "The device is being reset. This will take a minute."
    Label(loading_window, text=text, font=('times', 12)).pack()
    loading_window.update()
    time.sleep(3)
    print("The connection is complete!")

Tk().withdraw()
download_location = askdirectory(title='Find and select the download folder', mustexist=TRUE)

loading_window = Tk()
loading_window.after(200, threading())
loading_window.mainloop()

print("Finished")
acw1668
  • 40,144
  • 5
  • 22
  • 34
Justin Oberle
  • 502
  • 3
  • 22
  • FWIW, `loading_window.after(200, threading())` _immeidately_ calls `threading` instead of waiting 200ms. For the delay to work it needs to be `loading_window.after(200, threading)`. – Bryan Oakley Feb 20 '23 at 19:22
  • @BryanOakley I tried both, but it doesn't resolve my issue – Justin Oberle Feb 20 '23 at 19:27
  • It would save time to evaluate your code if you would show your imports – Jim Robinson Feb 20 '23 at 19:28
  • @JimRobinson My mistake. I added them now. Also, I have messed with many options and this keeps heppening to me. I removed the ".after" completely and ran the thread directly with the same issue. mainloop() wont stop running for some reason. – Justin Oberle Feb 20 '23 at 19:31
  • The main issue is that I do not know how to end a mainloop when no button is used for this. I basically need to end the mainloop() when the timer finishes. All the docs show how to do it with a button but not with a timer and it needs to be done with threading. – Justin Oberle Feb 20 '23 at 20:12
  • It is because you have two instances of `Tk()` and one of them is hidden, so you cannot destroy it manually. Therefore the `mainloop()` will not terminate. – acw1668 Feb 20 '23 at 23:05
  • @acw1668 Thank you. I think this is the obvious answer but it does not help unserstand where or how this is happening, or how to resolve it. – Justin Oberle Feb 21 '23 at 00:42
  • It is so obviously *where or how this is happening*. And *"How to resolve it"* - use only one instance of `Tk()`. – acw1668 Feb 21 '23 at 01:00
  • Yes but where would this be happening? I posted the code above. There is only one instance of Tk() right? – Justin Oberle Feb 21 '23 at 02:09
  • No, there are two instances: `Tk().withdraw()` (the hidden instance) and `loading_window = Tk()`. – acw1668 Feb 21 '23 at 02:34
  • @acw1668 Would you mind posting a solution. askDirectory opens 2 windows and all docs say to use withdraw to hide the unnecessary one. How should I resolve this? I think you have the answer to this issue I just cannot see exactly how. – Justin Oberle Feb 21 '23 at 02:47

2 Answers2

2

Since there are two instances of Tk():

  • Tk().withdraw() (hidden instance)
  • loading_window = Tk()

Even you close the second instance, mainloop() will still be running because there is a hidden instance of Tk().

You should use only one instance of Tk() instead:

import time
from threading import Thread
from tkinter.filedialog import askdirectory
from tkinter import *

def threading():
    t1=Thread(target=checkProgress)
    t1.start()

def checkProgress():
    loading_window.geometry("500x500")
    text = "The device is being reset. This will take a minute."
    Label(loading_window, text=text, font=('times', 12)).pack()
    loading_window.update()
    loading_window.deiconify()  # show the window
    time.sleep(3)
    print("The connection is complete!")
    loading_window.destroy()  # destroy the window

# only create one instance of Tk()
loading_window = Tk()
loading_window.withdraw() # hide the window
download_location = askdirectory(title='Find and select the download folder', mustexist=TRUE)
loading_window.after(200, threading)
loading_window.mainloop()

print("Finished")

Note that most people said it is not recommended to update tkinter widgets directly in a child thread because tkinter is not thread safe.

Below is the modified code to minimize direct update tkinter widgets in a thread:

import time
from threading import Thread
from tkinter.filedialog import askdirectory
from tkinter import *

def threading():
    loading_window.deiconify()  # show the window
    t1=Thread(target=checkProgress)
    t1.start()

def checkProgress():
    time.sleep(3)
    print("The connection is complete!")
    loading_window.after(10, loading_window.destroy)  # destroy the window


loading_window = Tk()
loading_window.geometry("500x500")
text = "The device is being reset. This will take a minute."
Label(loading_window, text=text, font=('times', 12)).pack()

loading_window.withdraw() # hide the window
download_location = askdirectory(title='Find and select the download folder', mustexist=TRUE)

loading_window.after(200, threading)
loading_window.mainloop()

print("Finished")
acw1668
  • 40,144
  • 5
  • 22
  • 34
  • This is exactly what I was looking for. The problem is resolved. Thank you very much. I think I spent all afternoon on this issue. – Justin Oberle Feb 21 '23 at 03:23
-1

You need something to destroy the window in order to exit the mainloop. You could add a button to close the window, or as shown below add a line to destroy the window when you are finished:

import tkinter as tk
from tkinter import filedialog
import time   
from threading import Thread        

def threading():
    t1=Thread(target=checkProgress)
    t1.start()

def checkProgress():
    loading_window.geometry = ("500x500")
    text = "The device is being reset. This will take a minute."
    tk.Label(loading_window, text=text, font=('times', 12)).pack()
    loading_window.update()
    time.sleep(3)
    # destroy the window
    loading_window.destroy()
    print("The connection is complete!")

if __name__=="__main__" :
    # tk().withdraw()
    download_location = filedialog.askdirectory(title='Find and select the download folder', mustexist=tk.TRUE)
    
    loading_window = tk.Tk()
    loading_window.after(200, threading)
    loading_window.mainloop()
    
    print("Finished") 
Jim Robinson
  • 189
  • 1
  • 10
  • Thank you for the response but this doesn't work. The script is still running once complete. – Justin Oberle Feb 20 '23 at 21:16
  • BTW, this is how I had it originally. The destroy does destroy the window but the script is still running in the background. If I use a button, it kills the script, or if I don't use threading, it kills the script. Maybe I have to use a button for this? – Justin Oberle Feb 20 '23 at 21:20
  • No, nothing is running on my machine after the set() event. – Hermann12 Feb 20 '23 at 21:32
  • @Hermann12 I am not sure why. On my machine the mainloop() still runs with your code. I posted a solution to this. It is extremely annoying I had to use a button. – Justin Oberle Feb 20 '23 at 21:55
  • @Hermann12 interestingly, if I add the tk.withdraw and the askidir code back to the top, even the button does not close the mainloop anymore. I think I should not have used tkinter at this point. It seems to have a lot of issues maybe. – Justin Oberle Feb 20 '23 at 22:19