1

I have a little app in python, which works fine except for this minor problem: it supposed to run a loop continuously, until the user tells it stop via button, but when I hit the start button, windows tells me it's not responding. Now if I write a simple print statement everything works fine:

   def update(self):
      self.bStart.config(state = 'disabled')
      self.bStop.config(state = 'active')

      self.running = True
      self.toRun = True
      while self.toRun:
          [...do some stuff...]
          time.sleep(self.toWait)
          print("i'm alive")

however, without the print statement, it just freezes and goes to 'not responding', and I am wandering whether there's a way to prevent that.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
KGS
  • 635
  • 4
  • 19
  • If you are in tkinter, why are you using time.sleep() instead of sitting in your app.mainloop()? – Alan Hoover Apr 27 '15 at 14:43
  • You should almost never call `time.sleep`, because it does, in fact, prevent your app from responding. – Bryan Oakley Apr 27 '15 at 15:34
  • @BryanOakley in that case what can I use as an alternative, that would make the tkinter wait for some time? – KGS Apr 27 '15 at 15:39
  • @KGS: in an event-based program, it is _always_ waiting. You shouldn't need to add artificial waits. If you want to do something after a period of time you can call the `after` method of a widget to trigger the function in the future. – Bryan Oakley Apr 27 '15 at 15:46
  • @BryanOakley Well in my case, I have some user input on a canvas; when the `start` button is pressed, the canvas starts updating itself continuously using the end state of each update as the starting point of the next, in a loop until the `stop` button is pressed. Without a waiting time, however, it all happens too fast, and the user is meant to observe the changes as they happen, so `time.wait()` seemed like a really good idea, because I then could include a slider which would configure how fast everything goes. I read a bit on the `after` method, and it doesn't seem to serve the same purpose – KGS Apr 27 '15 at 15:59

1 Answers1

1

As a general rule, you shouldn't be calling sleep in a GUI, and you shouldn't have loops that update the display. If you're periodically updating the display, proper use of after makes a loop unnecessary.

The general idea is to write a function that does a single update (ie: what you would otherwise have as the body of a loop). Then, have that function call itself via after.

Here's an example where you can see a bar chart grow over time, without using a loop:

import Tkinter as tk

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.canvas = tk.Canvas(self)
        self.start_button = tk.Button(self, text="start", command=self.on_start)
        self.stop_button = tk.Button(self, text="stop", command=self.on_stop)

        self.start_button.pack()
        self.stop_button.pack()
        self.canvas.pack(fill="both", expand=True)

        # call on_stop to initialize the state of the buttons
        self.on_stop()

    def on_start(self):
        """Start the animation"""
        self.canvas.delete("all")
        self.rect_id = self.canvas.create_rectangle(0,0,1,20, fill="blue")

        self.running = True
        self.start_button.configure(state="disabled")
        self.stop_button.configure(state="normal")
        self.draw_one_frame()

    def on_stop(self):
        """Stop the animation"""
        self.start_button.configure(state="normal")
        self.stop_button.configure(state="disabled")
        self.running = False

    def draw_one_frame(self):
        """Draw a single frame of animation"""
        (x0, y0, x1, y1) = self.canvas.coords(self.rect_id)
        if x1 < self.canvas.winfo_width():
            x1 += 1
        else:
            x1 = 1

        self.canvas.coords(self.rect_id, (x0, y0, x1, y1))

        if self.running:
            self.after(10, self.draw_one_frame)

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • thanks i get it now, i hadn't realized how `after` worked exactly, i'll try it out tomorrow (; – KGS Apr 27 '15 at 16:35