Michael Moreno
Michael Moreno

Reputation: 219

Clock Based Methods in Python TKinter Window - Please Critique

Goal: 30 minutes of timed activity that cycles through a series of alternating slow and intense exercises. The window starts by displaying a countdown timer starting at 30 minutes. The exercise starts with 30 seconds of "slow" activity - with the word "Slow" displayed below the timer. It then continues with 60 seconds of an intense exercise. Then 30 seconds of slow pace, followed by 60 seconds of a different intense exercise, and so on. In all, 4 different intense exercises each alternating with a slow pace.

I've assembled the code below. I don't believe it is intuitive. I don't like bouncing around methods and I feel there are methods out there that can be used to make this more efficient. On a practical level, this doesn't work well as the timer and the message (i.e. slow / exercise # displayed) start to become out of synch. I've researched python blogs and keep "hearing" something about Tkinter not being multithreaded. I'm guessing that the after(x,y) becomes compromised as the countdown thread takes up resources.... Not really sure. Also, I'd like to add a tone after each method as an audio queue for the "athletes" Anyway, thanks in advance for looking at my crude code.

 import Tkinter as tk

 class ExampleApp(tk.Tk):
     def __init__(self):
         tk.Tk.__init__(self)
         self.label = tk.Label(self, text="", width=10, font= ("Helvetica",72), background='yellow', fg = 'red')
         self.label2 = tk.Label(self, text="", width=10, font=("Helvetica",72), background='yellow', fg = 'blue')
         self.label.pack()
         self.label2.pack()
         self.remaining = 0
         self.countdown(1800)  # the 30 minutes countdown  initialization
         self.run = True 

     def countdown(self, remaining = None):
         if self.remaining == 1799:  # marker that starts the methods series
             self.start()
         if remaining is not None:
             self.remaining = remaining
         if self.remaining >= 1:
             mins = int(self.remaining/60)
             #rsecs = self.remaining - mins * 60 #could use this
             rsecs = self.remaining % 60     #same results as t - mins * 60
             self.remaining = self.remaining -1
             self.after(1000, self.countdown)
             self.label.configure(text ="%.2f" % (mins + rsecs / 100.0))
         else:
             self.label.configure(text="Stop")

     def start(self):
         app.after(0, self.slow1)
         return

     def slow1(self):
         self.label2.configure(text="Slow" )
         app.after(30000, self.fast1)
         return

     def fast1(self):
         self.label2.configure(text="Exercise 1" )
         app.after(61000, self.slow2)
         return

     def slow2(self):
         self.label2.configure(text="Slow" )
         app.after(31000, self.fast2)
         return

     def fast2(self):
         self.label2.configure(text="Exercise 2" )
         app.after(61000, self.slow3)
         return

     def slow3(self):
         self.label2.configure(text="Slow" )
         app.after(30000, self.fast3)
         return

     def fast3(self):
         self.label2.configure(text="Exercise 3" )
         app.after(60000, self.slow4)
         return

     def slow4(self):
         self.label2.configure(text="Slow" )
         app.after(30000, self.fast4)
         return

     def fast4(self):
         self.label2.configure(text="Exercise 4" )
         app.after(60000, self.slow1)
         return

 if __name__ == "__main__":
     app = ExampleApp()
     app.title("Intense Workout")
     app.geometry('550x550+200+200')
     app.configure(background='yellow')
     app.mainloop()

Upvotes: 1

Views: 380

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 386342

There are many ways to solve this problem, and you don't need threads. Here is one idea:

Start with a data structure that defines the intervals. For example:

intervals = [[30, "slow"], [60, "intense"], [30, "slow"], [60, "intense]]

Next, set up a simple timer function that is called once a second. At each second it displays the number and the string that is at the head of the list. The number represents the time left. In that timer function, subtract one from the number. If it becomes zero, remove that element from the list. Have this function call itself one second later using after, stopping when the data structure becomes empty (ie: when there is only one pair left and the number drops to zero).

The following gives you an idea about how to implement this. The code can be made better, but the point is to illustrate how to iterate over a data structure using after, not to necessarily give a production quality example.

import Tkinter as tk

class ExampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.label = tk.Label(self, text="", width=10, font= ("Helvetica",72), background='yellow', fg = 'red')
        self.label2 = tk.Label(self, text="", width=10, font=("Helvetica",72), background='yellow', fg = 'blue')
        self.label.pack()
        self.label2.pack()
        self.intervals = [[30, "Slow"], [60, "Exercise 1"],
                          [30, "Slow"], [60, "Exercise 2"],
                          [30, "Slow"], [60, "Exercise 3"],
                          [30, "Slow"], [60, "Exercise 4"],
                          ]
        self.countdown()

    def countdown(self):
        (remaining, label) = self.intervals[0]
        self.label.configure(text=remaining)
        self.label2.configure(text=label)
        remaining -= 1
        if remaining < 0:
            self.intervals.pop(0)
        else:
            self.intervals[0][0] = remaining
        if len(self.intervals) > 0:
            self.after(1000, self.countdown)
        else:
            self.label.configure(text="Done!")

if __name__ == "__main__":
    app = ExampleApp()
    app.title("Intense Workout")
    app.geometry('550x550+200+200')
    app.configure(background='yellow')
    app.mainloop()

Upvotes: 1

Related Questions