Husar
Husar

Reputation: 413

Python stopwatch example - starting all class instances at the same time?

In this example how can I start all 4 stop watches at the same time?

This example code is over 12 years old but it is the best stopwatch example I have been able to find via Google. You can see that I have 4 instances of the class in use. I need to be able to start all the instances at the exact same time. Tinker button doesn't allow for calling multiple functions. Even if it did it would be one function before the next so technically they wouldn't all start at the exact same time.

I will need to stop each stopwatch at different times but that is easy by just calling each Stop function in the class. But I can't figure out how to start them all at the same time.

from Tkinter import *
import time

class StopWatch(Frame):
    """ Implements a stop watch frame widget. """
    def __init__(self, parent=None, **kw):
        Frame.__init__(self, parent, kw)
        self._start = 0.0
        self._elapsedtime = 0.0
        self._running = 0
        self.timestr = StringVar()
        self.makeWidgets()

    def makeWidgets(self):
        """ Make the time labels. """
        l = Label(self, textvariable=self.timestr)

        l.pack(fill=X, expand=NO, pady=2, padx=2)


        self._setTime(self._elapsedtime)

    def _update(self):
        """ Update the label with elapsed time. """
        self._elapsedtime = time.time() - self._start
        self._setTime(self._elapsedtime)
        self._timer = self.after(50, self._update)

    def _setTime(self, elap):
        """ Set the time string to Minutes:Seconds:Hundreths """
        minutes = int(elap/60)
        seconds = int(elap - minutes*60.0)
        hseconds = int((elap - minutes*60.0 - seconds)*100)
        self.timestr.set('%02d:%02d:%02d' % (minutes, seconds, hseconds))

    def Start(self):
        global sw2
        """ Start the stopwatch, ignore if running. """
        if not self._running:
            self._start = time.time() - self._elapsedtime
            self._update()
            self._running = 1


    def Stop(self):
        """ Stop the stopwatch, ignore if stopped. """
        if self._running:
            self.after_cancel(self._timer)
            self._elapsedtime = time.time() - self._start
            self._setTime(self._elapsedtime)
            self._running = 0

    def Reset(self):
        """ Reset the stopwatch. """
        self._start = time.time()
        self._elapsedtime = 0.0
        self._setTime(self._elapsedtime)


def main():


    root = Tk()
    sw1 = StopWatch(root)
    sw1.pack(side=TOP)

    sw2 = StopWatch(root)
    sw2.pack(side=TOP)

    sw3 = StopWatch(root)
    sw3.pack(side=TOP)

    sw4 = StopWatch(root)
    sw4.pack(side=TOP)

    Button(root, text='Start', command=sw1.Start).pack(side=LEFT)
    Button(root, text='Stop', command=sw1.Stop).pack(side=LEFT)
    Button(root, text='Reset', command=sw1.Reset).pack(side=LEFT)
    Button(root, text='Quit', command=root.quit).pack(side=LEFT)

    root.mainloop()

if __name__ == '__main__':
    main()

Upvotes: 1

Views: 1342

Answers (3)

Husar
Husar

Reputation: 413

Here is what I ended up doing based on Bryan's idea of having one instance of the counter and then taking splits of the time. This works but I have not figured out a way to just use on Getspit function to grab each time. Maybe passing in the a,b,c,d and then an if to get the time? Right now I am doing this with buttons but once implemented it will be grabbing them via events that happen in the main program I am writing. If anyone has any improvements on this please let me know. Thanks to everyone for the help.

 from Tkinter import *
 import time
 import tkMessageBox

 class StopWatch(Frame):
     """ Implements a stop watch frame widget. """
     def __init__(self, parent=None, **kw):
    Frame.__init__(self, parent, kw)
    self._start = 0.0
    self._elapsedtime = 0.0
    self._running = 0
    self.timestr = StringVar()
    self.lapastr = StringVar()
    self.lapbstr = StringVar()
    self.lapcstr = StringVar()
    self.lapdstr = StringVar()
    self.makeWidgets()

def makeWidgets(self):
    """ Make the time labels. """
    la = Label(self, textvariable=self.timestr)
    la.pack(fill=X, expand=NO, pady=2, padx=2)
    #self._setTime(self._elapsedtime)

    lb = Label(self, textvariable=self.timestr)
    lb.pack(fill=X, expand=NO, pady=2, padx=2)
    #self._setTime(self._elapsedtime)

    lc = Label(self, textvariable=self.timestr)
    lc.pack(fill=X, expand=NO, pady=2, padx=2)
    #self._setTime(self._elapsedtime)

    ld = Label(self, textvariable=self.timestr)
    ld.pack(fill=X, expand=NO, pady=2, padx=2)

    lsplita = Label(self, textvariable=self.lapastr)
    lsplita.pack(fill=X, expand=NO, pady=2, padx=2)

    lsplitb =Label(self, textvariable=self.lapbstr)
    lsplitb.pack(fill=X, expand=NO, pady=2, padx=2)

    lsplitc = Label(self, textvariable=self.lapcstr)
    lsplitc.pack(fill=X, expand=NO, pady=2, padx=2)

    lsplitd = Label(self, textvariable=self.lapdstr)
    lsplitd.pack(fill=X, expand=NO, pady=2, padx=2)

    self._setTime(self._elapsedtime)

def _update(self):
    """ Update the label with elapsed time. """
    self._elapsedtime = time.time() - self._start
    self._setTime(self._elapsedtime)
    self._timer = self.after(50, self._update)

def _setTime(self, elap):
    """ Set the time string to Minutes:Seconds:Hundreths """
    minutes = int(elap/60)
    seconds = int(elap - minutes*60.0)
    hseconds = int((elap - minutes*60.0 - seconds)*100)

    self.timestr.set('%02d:%02d:%02d' % (minutes, seconds, hseconds))

def Start(self):
    """ Start the stopwatch, ignore if running. """
    if not self._running:
        self._start = time.time() - self._elapsedtime
        self._update()
        self._running = 1

def Stop(self):
    """ Stop the stopwatch, ignore if stopped. """
    if self._running:
        self.after_cancel(self._timer)
        self._elapsedtime = time.time() - self._start
        self._setTime(self._elapsedtime)
        self._running = 0

def Getsplita(self):
    """ Stop the stopwatch, ignore if stopped. """
    if self._running:
        self._lapstr = time.time() - self._start
        self._setTime(self._elapsedtime)
        self.lapastr.set(self._lapstr)

def Getsplitb(self):
    """ Stop the stopwatch, ignore if stopped. """
    if self._running:
        self._lapstr = time.time() - self._start
        self._setTime(self._elapsedtime)
        self.lapbstr.set(self._lapstr)

def Getsplitc(self):
    """ Stop the stopwatch, ignore if stopped. """
    if self._running:
        self._lapstr = time.time() - self._start
        self._setTime(self._elapsedtime)
        self.lapcstr.set(self._lapstr)

def Getsplitd(self):
    """ Stop the stopwatch, ignore if stopped. """
    if self._running:
        self._lapstr = time.time() - self._start
        self._setTime(self._elapsedtime)
        self.lapdstr.set(self._lapstr)

def Reset(self):
    """ Reset the stopwatch. """
    self._start = time.time()
    self._elapsedtime = 0.0
    self._setTime(self._elapsedtime)

def main():

     root = Tk()
     sw1 = StopWatch(root)
     sw1.pack(side=TOP)

     Button(root, text='Start', command=sw1.Start).pack(side=LEFT)
     Button(root, text='Stop', command=sw1.Stop).pack(side=LEFT)
     Button(root, text='Reset', command=sw1.Reset).pack(side=LEFT)
     Button(root, text='Quit', command=root.quit).pack(side=LEFT)
     Button(root, text='Get Split A', command=sw1.Getsplita).pack(side=LEFT)
     Button(root, text='Get Split B', command=sw1.Getsplitb).pack(side=LEFT)
     Button(root, text='Get Split C', command=sw1.Getsplitc).pack(side=LEFT)
     Button(root, text='Get Split D', command=sw1.Getsplitd).pack(side=LEFT)
root.mainloop()

if __name__ == '__main__':
     main()

Upvotes: 1

Bryan Oakley
Bryan Oakley

Reputation: 385960

"Tinker [sic] button doesn't allow for calling multiple functions."

No, but it can call a single function which can call multiple functions.

def start():
    for sw in (sw1, sw2, sw3, sw4):
        sw.Start()

...
Button(root, text='Start', command=start).pack(side=LEFT)

You are correct that it is impossible to start them at precisely the same time. Though, they will all be within a millisecond or two of each other so for most situations it's quite good enough. Since you're only displaying the time to the granularity of a whole second, the timers should always show the same time.

If you really need them to be synchronized, you need them to share the same exact start time. You can do that by allowing the stopwatch to be given a start time. Then, you can compute the start time once and pass the same value to all four watches:

class Stopwatch(Frame):
    ...
    def Start(self, starttime=None):
        ...
        if not self._running:
            if starttime is None:
                self._start = time.time() - self._elapsedtime
            else:
                self._start = starttime - self.elapsedtime
            ...

    ...
t = time.time()
sw1.Start(t)
sw2.Start(t)
sw3.Start(t)
sw4.Start(t)

You could also have a single timer object that all of the stopwatches share. They then aren't so much stopwatches as they are "split timers" -- they can't control the starting of the watch, they can only record the interval that they were stopped at.

Upvotes: 1

Noctis Skytower
Noctis Skytower

Reputation: 22001

The following program may be close to want you want. Please note that since it takes time to start and stop the stopwatches, you may find small discrepancies among the times they are showing.

#! /usr/bin/env python3
import tkinter
import time

class StopWatch(tkinter.Frame):

    @classmethod
    def main(cls):
        tkinter.NoDefaultRoot()
        root = tkinter.Tk()
        root.title('Stop Watch')
        root.resizable(True, False)
        root.grid_columnconfigure(0, weight=1)
        padding = dict(padx=5, pady=5)
        widget = StopWatch(root, **padding)
        widget.grid(sticky=tkinter.NSEW, **padding)
        root.mainloop()

    def __init__(self, master=None, cnf={}, **kw):
        padding = dict(padx=kw.pop('padx', 5), pady=kw.pop('pady', 5))
        super().__init__(master, cnf, **kw)
        self.grid_columnconfigure(1, weight=1)
        self.grid_rowconfigure(1, weight=1)
        self.__total = 0
        self.__label = tkinter.Label(self, text='Total Time:')
        self.__time = tkinter.StringVar(self, '0.000000')
        self.__display = tkinter.Label(self, textvariable=self.__time)
        self.__button = tkinter.Button(self, text='Start', command=self.click)
        self.__label.grid(row=0, column=0, sticky=tkinter.E, **padding)
        self.__display.grid(row=0, column=1, sticky=tkinter.EW, **padding)
        self.__button.grid(row=1, column=0, columnspan=2,
                           sticky=tkinter.NSEW, **padding)

    def click(self):
        if self.__button['text'] == 'Start':
            self.__button['text'] = 'Stop'
            self.__start = time.clock()
            self.__counter = self.after_idle(self.__update)
        else:
            self.__button['text'] = 'Start'
            self.after_cancel(self.__counter)

    def __update(self):
        now = time.clock()
        diff = now - self.__start
        self.__start = now
        self.__total += diff
        self.__time.set('{:.6f}'.format(self.__total))
        self.__counter = self.after_idle(self.__update)

class ManyStopWatch(tkinter.Tk):

    def __init__(self, count):
        super().__init__()
        self.title('Stopwatches')
        padding = dict(padx=5, pady=5)
        tkinter.Button(self, text='Toggle All', command=self.click).grid(
            sticky=tkinter.NSEW, **padding)
        for _ in range(count):
            StopWatch(self, **padding).grid(sticky=tkinter.NSEW, **padding)

    def click(self):
        for child in self.children.values():
            if isinstance(child, StopWatch):
                child.click()

if __name__ == '__main__':
    ManyStopWatch(4).mainloop()

Upvotes: 1

Related Questions