SquareCrow
SquareCrow

Reputation: 311

Tkinter Loop Never Exits Cleanly

Python 2.7

I have written a run method for my Tkinter GUI rather than using the standard mainloop, and it always exits on an error when I close the window, even after implementing a WM_DELETE_WINDOW protocol as advised elsewhere on SO. I tried invoking exit in the protocol callback and returning from the loop, but Python always goes through the loop one last time. Why is this?

class FrameApp(object):
    def __init__(self):
        ...
        self.rootWin.protocol("WM_DELETE_WINDOW", self.callback_destroy)
        self.winRunning = False

    def callback_destroy(self):
        self.winRunning = False
        self.rootWin.destroy() # go away, window
        exit() # GET OUT

Here is the run loop:

    def run(self):
        last = -infty
        self.winRunning = True
        ...
        while self.winRunning:
            # 4.a. Calc geometry
            self.calcFunc( self.get_sliders_as_list() )
            # 4.b. Send new coords to segments
            self.simFrame.transform_contents()
            # 4.d. Wait remainder of 40ms
            elapsed = time.time() * 1000 - last
            if elapsed < 40:
                time.sleep( (40 - elapsed) / 1000.0 )
            # 4.e. Mark beginning of next loop
            last = time.time() * 1000
            # 4.f. Update window
            if not self.winRunning: # This does not solve the problem 
                return # still tries to call 'update', 
                #        and never exits cleanly
            self.canvas.update() 
            # don't know how to prevent these from being called 
            # again after the window is destroyed
            self.rootWin.update_idletasks()

Result:

File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 972, in update_idletasks self.tk.call('update', 'idletasks') _tkinter.TclError: can't invoke "update" command: application has been destroyed

Upvotes: 0

Views: 886

Answers (1)

Delioth
Delioth

Reputation: 1554

Without the mainloop, tkinter cannot get the WM_DELETE_WINDOW message to call your exit function. (Or rather, it can only catch anything during the ~millisecond of the update_idletasks call, as it won't queue since tkinter doesn't have an event loop (and thus queue) going since you never start one.) It can't catch signals if it can't communicate with the Window Manager (system), and it can't communicate if it isn't looping.

To solve it, just use the event/main loop. Make your run function save any state it needs and call itself after whatever interval you wish.


On another note, don't use time.sleep with tkinter- it prevents it from doing anything (and also the 40ms remaining of sleep is probably longer than the rest of the loop, so you'd have 41 ms of waiting and 0.5 ms of clickability). Instead, just carefully configure your root.after statements (you can calculate things in them, too)

Upvotes: 1

Related Questions