5

I have a python code that includes tkinter window and other running tasks.

I've been trying to bind "WM_DELETE_WINDOW" event to a function that exits my python code when I close the window but can't achieve that.

This is what I try:

def on_exit():
    root.destroy()
    sys.exit()
root.protocol('WM_DELETE_WINDOW', on_exit)

The window is destroyed successfully but the python code doesn't exit. Any possible reason for sys.exit() not to work?

What am I doing wrong? any alternative approach should I try?

Doing some testing I figured out what can be the problem.

Here's a small code that summarizes my code which is much bigger.

import tkinter as tk
import sys

root = tk.Tk()
submitted = tk.IntVar()

def on_exit():
    root.destroy()
    sys.exit()
root.protocol('WM_DELETE_WINDOW', on_exit)

def submit():
    submitted.set(1)
    print("submitted")

button= tk.Button(root, text="Submit",command=submit)
button.pack()
button.wait_variable(submitted)

root.mainloop()

I believe now that wait_variable is the source of the problem.

And the code actually exits when I added submitted.set(1) to on_exit() ( or if I clicked the button first before closing the window ) but if I tried closing the window without pressing the button, the code won't exit.

So does this mean that wait_variable not only makes tkinter app wait, but also prevents python code exiting?!

I tried os._exit(1) and it worked, but I think it's not clean.

martineau
  • 119,623
  • 25
  • 170
  • 301
David Sidarous
  • 1,202
  • 1
  • 10
  • 25
  • 1
    What do you mean by "the python code doesn't exit"? What code is running if you destroy the window? What are the "other tasks"? Perhaps you need to stop them in addition to stopping tkinter. Please provide a [mcve]. – Bryan Oakley May 16 '19 at 19:24
  • Any possible reason `sys.exit()` doesn't work ? – David Sidarous May 16 '19 at 19:26
  • `root.destroy()` will close your tk app and all its child widgets. `sys.exit()` is used to stop anything running in python. Maybe there is an issue with `sys.exit()` happening in the function before the mainloop. Like everything in the mainloop is ended so its causing a problem with sys.exit? Try putting `sys.exit()` right after your `mainloop()`. – Mike - SMT May 16 '19 at 19:33
  • @Mike-SMT Will try this now! Thanks. But when I even try putting it before `root.destroy()` it won't work. is that normal ? – David Sidarous May 16 '19 at 19:33
  • Try `root.quit()` I think `destroy` simply destroys the window. – tgikal May 16 '19 at 19:37
  • 2
    @tgikal: `destroy` will destroy the window and stop `mainloop`. `quit` only stops `mainloop` – Bryan Oakley May 16 '19 at 19:38
  • @tgikal I think `root.quit()` only makes the mainloop exit. It does not actually terminate the interpreter. – Mike - SMT May 16 '19 at 19:40
  • you might want to search this site for questions relating to why sys.exit doesn't seem to work. There are several related questions. – Bryan Oakley May 16 '19 at 19:40
  • @BryanOakley Already did, Thanks for the advice. But it seems from your experience, nothing related to tkinter specifically prevents the normal operation of `sys.exit()` , No ? – David Sidarous May 16 '19 at 19:42
  • All my testing sys.exit() is working fine to close the app. Maybe I am not understanding your need. – Mike - SMT May 16 '19 at 19:43
  • AFAIK, nothing in tkinter specifically prevents the normal operation of sys.exit. – Bryan Oakley May 16 '19 at 19:44
  • @Mike-SMT Did you tested the same way ? by capturing the 'WM_DELETE_WINDOW' event ? It works with me too from anywhere else. but not from within this function – David Sidarous May 16 '19 at 19:45
  • Probably because of this: https://stackoverflow.com/a/905224/982257 You could try `os._exit()` instead. This directly exits the process without raising a `SystemExit` exception. – Iguananaut May 16 '19 at 19:46
  • @BryanOakley Thanks for trying to help. it's just weird why `sys.exit()` works everywhere in the script. but not from within `on_exit()` function. – David Sidarous May 16 '19 at 19:46
  • FWIW, the docs say `sys.exit()` only works when called from the main thread—are you using threads by any chance? – martineau May 16 '19 at 19:50
  • I think there's more to the story than you are telling us. I can show a trivial example of a tkinter program that proves `sys.exit()` does work in a protocol handler. Please provide a _complete_ [mcve]. – Bryan Oakley May 16 '19 at 20:00
  • Also, please describe how you are running the program—i.e. from the command line or within an IDE, like IDLE or pycharm. – martineau May 16 '19 at 20:13
  • Updated the code with a code that summarizes the problem It looks it actually has something to do with tkinter @BryanOakley – David Sidarous May 16 '19 at 20:14
  • @DavidSidarous I did test with `WM_DELETE_WINDOW` event and using the function as well. – Mike - SMT May 16 '19 at 20:19
  • @Mike-SMT check the updated question please, I appreciate your help – David Sidarous May 16 '19 at 20:20
  • @DavidSidarous your code in your updated question exits normally for me. I get `Process finished with exit code 0` as I expect to get in my IDE. What OS / Version are you on? – Mike - SMT May 16 '19 at 20:22
  • @Mike-SMT you must have pressed the button first. but if you tried closing the window without the button is pressed it won't work – David Sidarous May 16 '19 at 20:24
  • @DavidSidarous ahh Yes I see that now. Hum. So what is the point of using `wait_variable()` In over 2 years of developing in tkinter I have never use this nor have I seen it used in a question. Judging by the documentation thought it appears to be working as intended. – Mike - SMT May 16 '19 at 20:26
  • _"So what is the point of using wait_variable() "_ - it is one of a couple of ways in which you can implement a function will not return until a user enters data. A classic example is a modal dialog box. – Bryan Oakley May 16 '19 at 21:11

1 Answers1

3

As your updated question points out the problem is wait_variable(). Going off the documentation for this method wait_variable() enters a local event loop that wont interrupt the mainloop however it appears that until that local event loop is terminated (the variable is updated in some way) it will prevent the python instance from terminating as there is still an active loop. So in order to prevent this you have also correctly pointed out you need to update this variable right before you terminate the tk instance.

This might seam odd but it is the behavior I would expect. It is my understanding that an active loop needs to be terminated before a python instance can exit.

As Bryan has pointed out in the comments the wait_variable() method is "a function which calls the vwait command inside the embedded tcl interpreter. This tcl interpreter knows nothing about python exceptions which is likely why it doesn't recognize the python exception raised by sys.exit()"

Link to relevant documentation:

wait_variable()

Relevant text from link:

wait_variable(name)

Waits for the given Tkinter variable to change. This method enters a local event loop, so other parts of the application will still be responsive. The local event loop is terminated when the variable is updated (setting it to it’s current value also counts).

You can also set the variable to whatever it is currently set as to terminate this event loop.

This line should work for you:

submitted.set(submitted.get())

That said you do not actually need sys.exit(). You can simply use root.destroy().

You new function should look like this:

def on_exit():
    submitted.set(submitted.get())
    root.destroy()

The python instance will automatically close if there is no more code after the mainloop.

martineau
  • 119,623
  • 25
  • 170
  • 301
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • Thanks Mike, That's what I realized. But still can't see why would it prevent python from terminating. Very weird behavior! – David Sidarous May 16 '19 at 20:35
  • @DavidSidarous it prevents python from termination because there is an active loop. You have to terminate this loop first before python can close. – Mike - SMT May 16 '19 at 20:36
  • Thanks Mike, That's what I thought too. I tried `os._exit(1)` and it worked, but I think it's not clean. So I'll stick with simply setting the variable. – David Sidarous May 16 '19 at 20:48
  • _"My thoughts is that because it is its own local loop separate from tkinter"_ - that's not a correct summary. `wait_variable` is part of tkinter, and is little more than a second invocation of `mainloop` with an additional check that it won't return until the condition is satisfied. – Bryan Oakley May 16 '19 at 21:07
  • @BryanOakley What's your justification then ? why does it behave this way and won't allow sys.exit() ? – David Sidarous May 16 '19 at 21:46
  • @DavidSidarous: I don't know how to answer your question. My guess is, the handling of the exception thrown by `sys.exit` is handled by `mainloop`, so until that loop is re-entered, it can't handle the exception. As long as tkinter is waiting on the variable, the original `mainloop` isn't able to process events. – Bryan Oakley May 16 '19 at 21:51
  • @BryanOakley My guess is the local loop generated is like a separate thread, so exception thrown by `sys.exit` isn't recognized by mainthread. I don't know a possible way of signalling from the event loop of `wait_variable` to the main thread. but if there's a way it would work I guess. – David Sidarous May 16 '19 at 21:55
  • 1
    @DavidSidarous: tkinter is single threaded. It's not a separate thread. It's simply a function that has it's own event loop. More accurately, it's a function which calls the [vwait](http://tcl.tk/man/tcl8.5/TclCmd/vwait.htm) command inside the embedded tcl interpreter. This tcl interpreter knows nothing about python exceptions which is likely why it doesn't recognize the python exception raised by `sys.exit()`. I suppose conceptually you might think of it as a thread, but from a literal point of view it is running in the main thread. – Bryan Oakley May 16 '19 at 22:11
  • @BryanOakley Awesome explanation! – David Sidarous May 16 '19 at 22:29
  • @BryanOakley thanks Bryan for the link. I will read over it. I have never had to use this method before so all the information I had to go on is the short definition in the methods list. – Mike - SMT May 16 '19 at 22:43