1

I'm having some issue with the close button of the interface window with tkinter. My tool displays some video in real time, I do that with an infinite loop with the after function.

When I close the tkinter window by clicking on the cross, the program freezes. However, when I click on the button, the same function is called but it closes properly.

Here is the most simplified code I came up to show you the problem. Does anyone have an explanation and a way to solve it?

(BTW, I'm using Python 2.7.8 on OSX)

from Tkinter import *
from PIL import Image, ImageTk
import numpy as np

class Test():
    def __init__(self, master):
        self.parent = master
        self.frame = Frame(self.parent)
        self.frame.pack(fill=BOTH, expand=1)
        self.mainPanel = Label(self.frame)
        self.mainPanel.pack(fill=BOTH, expand=1)
        self.closeButton = Button(self.frame, command=self.closeApp)
        self.closeButton.pack(fill=BOTH, expand=1)

    def closeApp(self):
        print "OVER"
        self.parent.destroy()

def task(tool):
    print 'ok'
    im = Image.fromarray(np.zeros((500, 500, 3)), 'RGB')
    tool.tkim = ImageTk.PhotoImage(im)
    tool.mainPanel['image'] = tool.tkim
    root.after(1, task, tool)

def on_closing():
    print "OVER"
    root.destroy()

root = Tk()
root.wm_protocol("WM_DELETE_WINDOW", on_closing)
tool = Test(root)
root.after(1, task, tool)
root.mainloop()

Now if you try again with a smaller image (say 100*100), it works. Or if you put a delay of 100 in the after function, it also works. But in my application, I need a really short delay time as I'm displaying a video and my image size is 900px*500px.

Thanks!

Edit (08/19) : I have not found the solution yet. But I may use root.overrideredirect(1) to remove the close button and then recreate it in Tk, and also add drag a window using : Python/Tkinter: Mouse drag a window without borders, eg. overridedirect(1)

Edit (08/20) : Actually, I can not even drag the window. The tool is also freezing!

Community
  • 1
  • 1
Hugo
  • 153
  • 2
  • 9
  • http://stackoverflow.com/a/17016127/4731042 See also Bryan Oakley: "I recommend against using after with 1ms unless you truly need to do something 1000 times a second." in http://stackoverflow.com/questions/28047746/access-to-tkinters-text-widget-by-background-thread-cause-crashes – Eric Levieil Aug 14 '15 at 17:30
  • Thanks for the links. In my case, it's freezing undefinitely, probably because of the infinite loop. If it was only freezing for a recursion of the task function, it would not be a problem at all. Bryan Oakley is probably right. The problem is that I really need a delay < 25ms, as my video is in 40fps. – Hugo Aug 14 '15 at 18:25

2 Answers2

2

You probably just need to kill your animation loop. after returns a job id which can be used to cancel pending jobs.

def task():
    global job_id
    ...
    job_id = root.after(1, task, tool)

def on_closing():
    global job_id
    ...
    root.after_cancel(job_id)

Your code could be a bit cleaner if these functions were methods of the object so you didn't have to use a global variable. Also, you should have one quit function rather than two. Or, have one call the other so you are certain both go through exactly the same code path.

Finally, you shouldn't be calling a function 1000 times a second unless you really need to. Calling it so often will make your UI sluggish.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • This is great, thanks! And thanks for the tips. Actually I will call after function with `25 - app_time` (where `app_time` is the time of treatment of my image) because I need the video to be in 40 fps. – Hugo Aug 19 '15 at 12:23
  • I'm sorry, it is not working, for some test I put 20ms and it worked but setting back some smaller value, it is not working anymore. Actually, the on_closing function is not even called: the "OVER" is never printed. Do you have an idea of a solution? – Hugo Aug 19 '15 at 12:40
1

I found a solution, I'm not sure it is really clean, but at least it is working for what I want to do. I no longer use after but I loop and update the gui at each iteration.

from Tkinter import *
from PIL import Image, ImageTk
import numpy as np


class Test():
    def __init__(self, master):
        self.parent = master
        self.frame = Frame(self.parent)
        self.frame.pack(fill=BOTH, expand=1)
        self.mainPanel = Label(self.frame)
        self.mainPanel.pack(fill=BOTH, expand=1)
        self.parent.wm_protocol("WM_DELETE_WINDOW", self.on_closing)
        self.close = 0

    def on_closing(self):
        print "Over"
        self.close = 1

    def task(self):
        print "ok"
        im = Image.fromarray(np.zeros((500, 500, 3)), 'RGB')
        self.tkim = ImageTk.PhotoImage(im)
        self.mainPanel['image'] = self.tkim

root = Tk()
tool = Test(root)

while(tool.close != 1):
    tool.task()
    root.update()
root.destroy()
Hugo
  • 153
  • 2
  • 9