-->

Have after() method run on time Tkinter

2019-09-13 20:38发布

问题:

I'm programming a tool that has to display a timer with 1/100 of second as precision in Tkinter with python. I've tried using the after() method with self.timerLabel.after(0, self.update_timer)

def update_timer (self):
        self.timer += 0.01
        self.timerLabel.configure(text="%.2f" % self.timer)
        self.timerLabel.after(10, self.update_timer)

The problem is that it runs way slower than expected and I need to know if there's a workaround or some way to have the timer run exactly on time. Or maybe some way to use the computer's time to display the correct elapsed time on the screen. Thank you in advance

回答1:

The most accurate method is probably to find a start time, then every time you call the update timer function just subtract the start time from the current time.

I've attached some example code to show you roughly how it would work. It should be fairly simple to adapt that to use after() for Tkinter.

import time

# Start time
startTime = time.time();

def update_timer():

  # Find difference and print
  timeDifference = time.time() - startTime;
  print(timeDifference);

  # Sleep appropriate amount of time before printing
  # next statement.
  time.sleep(0.1);

  # Recursively call update.
  update_timer();

# Start running
update_timer();

Example fiddle: https://repl.it/Hoqh/0



回答2:

The after command makes no guarantees, other than it will not execute before the given time period. Tkinter is not a real time system.

You also have to take into account the time it takes to execute the code. For example, let's assume that your command starts precisely on time. In that code you have two lines of code:

self.timer += 0.01
self.timerLabel.configure(text="%.2f" % self.timer)

That code takes some amount of time to execute. It may be only a few microseconds, but it's definitely more than zero microseconds.

You then call after again:

self.timerLabel.after(10, self.update_timer)

The next time it will run will be at least 10 ms after the current time, which is a few microseconds or milliseconds after it was called. So, if the first two commands take 1ms, then the next time you call it will be 11 ms after the first call (10ms for the delay, plus 1ms for the code to execute)

You can minimize the delay factor a little by calling after immediately, rather than waiting for the rest of the code to execute. As long as the delay is greater than the time that it takes to execute the other code, you'll notice a slight improvement:

def update_timer (self):
    self.timerLabel.after(10, self.update_timer)
    self.timer += 0.01
    self.timerLabel.configure(text="%.2f" % self.timer)

This still won't be precise, since there are other things that can prevent self.timer from being called exactly 10ms after it was requested. For example, if the window is resized or moved at 9.99ms, tkinter will have to handle the redraw before it can handle the scheduled task.

If you want to account for all of this drift, don't just automatically increment by 10ms each time. Instead, calculate the time between each invocation, and add the delta.