简体   繁体   中英

Python tKinter: How to pause countdown timer

I made a countdown timer that starts whenever I press on the space key on my keyboard, but the problem is that I can't do anything on the program until the timer ends. I want make it pause when the space key is pressed a second time.

The countdown timer that I made works in a while loop that ends when the timer reach 0, so the program waits until the loop ends before doing anything else, even if I want to stop the timer I can't do it while it's running.

Here's the code

from tkinter import *
from tkinter import ttk
import tkinter as tk
from PIL import ImageTk, Image


def StartTimer():
    if (root.turn % 2) == 0: #Turn to white
        root.number = '1'
        root.color = 'white'
    else: #Turn to black
        root.number = '2'
        root.color = 'black'
    doTimer()

def doTimer():
    root.time = root.minute *60 + root.second
    root.turn = root.turn+1

    number = root.number

    root.rndsquare1.configure(image=root.green)
    root.timer1.configure(bg='#1C953D')
    root.white.configure(bg='#1C953D')
    r=0
    while r < root.time:
        root.update_idletasks()
        root.after(1000)
        root.second = root.second - 1
        if root.second == -1:
            root.minute = root.minute -1
            root.second = 59

        root.time1 = ''
        if len(str(root.minute)) == 1:
            root.time1 = '0' + str(root.minute)
        else:
            root.time1 = str(root.minute)
        if len(str(root.second)) == 1:
            root.time1 = root.time1 + ':' + '0' + str(root.second)
        else:
            root.time1 = root.time1 + ':' + str(root.second)
        
        root.timer1.configure(text=root.time1)
        r=r+1
    
    root.timer1.configure(bg='#454545')
    root.white.configure(bg='#454545')
    root.rndsquare1.configure(image=root.grey)



class root(Tk):
    def __init__(self):
        super(root, self).__init__()

        self.title("Chess Clock")
        self.minsize(1539,600)
        self.windowBG = '#313131'
        self.state('zoomed')
        self.configure(bg=self.windowBG)

        self.CreateWindow()

    def CreateWindow(self):
        self.grey = ImageTk.PhotoImage(Image.open(r"D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square grey.png"))
        self.green = ImageTk.PhotoImage(Image.open(r"D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square green.png"))
        self.turn=0

        self.rndsquare1 = Label(self, image=self.grey, borderwidth=0)
        self.rndsquare1.place(x=65, y=120)
        self.rndsquare2 = Label(self, image=self.grey, borderwidth=0)
        self.rndsquare2.place(x=809, y=120)

        self.bind('<space>',lambda event:StartTimer())
        self.createTimers()



    def createTimers(self):
        self.minute = 1
        self.second = 5

        self.time1 = ''
        if len(str(self.minute)) == 1:
            self.time1 = '0' + str(self.minute)
        else:
            self.time1 = str(self.minute)
        if len(str(self.second)) == 1:
            self.time1 = self.time1 + ':' + '0' + str(self.second)
        else:
            self.time1 = self.time1 + ':' + str(self.second)

        self.time2 = ''
        if len(str(self.minute)) == 1:
            self.time2 = '0' + str(self.minute)
        else:
            self.time2 = str(self.minute)
        if len(str(self.second)) == 1:
            self.time2 = self.time2 + ':' + '0' + str(self.second)
        else:
            self.time2 = self.time2 + ':' + str(self.second)

        self.timer1 = Label(self, text=self.time1, bg='#454545', fg='white', font ="Gadugi 40 bold")
        self.timer1.place(x=330, y=420)
        
        self.timer2 = Label(self, text=self.time2, bg='#454545', fg='white', font ="Gadugi 40 bold")
        self.timer2.place(x=1080, y=420)

        self.white = Label(self, text='White', bg='#454545', fg='white', font ="Gadugi 40 bold")
        self.white.place(x=325, y=160)

        self.black = Label(self, text='Black', bg='#454545', fg='white', font ="Gadugi 40 bold")
        self.black.place(x=1075, y=160)



root=root()
root.mainloop()

D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square grey.png

D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square green.png

You can solve this by heavily refacturing your code. You can add 2 clocks to your widget, each clock tracks how much is spent on itself. The spacebar listener simply switches between which clock is currently in use. By also having a timed do_clock_logic every 200ms or so it checks if a current clock is set, if so if the time is up and if that is the case, switch over to the other clock. In any case it will trigger the clocks tick() method to update its internal states that also handle ui updates.

This way there is no "blocking" while loop and all timing stuff is handled by tk:

from tkinter import Tk, Label
import tkinter as tk
from PIL import ImageTk, Image
from datetime import datetime, timedelta

class clock():
    """A single clock that handles updating/timekeeping itself. It uses
    the both class-level memebrs as active/inactive image and has
    references provided to place the image and the timing text."""
    active_img = None
    deactive_img = None

    @staticmethod
    def format_time(delta, ms = False):
        """Returns a formatted strng for a timedelta instance, 
        optionally with milliseconds"""

        return f"{delta.seconds//60:02}:{delta.seconds%60:02}" + (
                f".{(delta.microseconds // 1000):04}" if ms else "")

    def __init__(self, minutes, seconds, bg_lbl, text_lbl):
        """Set the clocks max duration providing 'minutes' and 'seconds'.
        Provide tk-labels with a background image 'bg_lbl' and
        'text_lbl' for the time display."""
        self.max_duration = timedelta(seconds=seconds+minutes*60)
        # UI
        self.bg_lbl = bg_lbl, 
        self.text_lbl = text_lbl
        # reset to inactive image and no text
        self.bg_lbl[0].config(image = clock.deactive_img)
        self.text_lbl.config(text = "")
        # internal time keeping of total spent time1
        self.total = timedelta()   # 0 delta at start

    def update_lbl(self, spent):
        # update the image if needed
        self.bg_lbl[0].config(image = clock.active_img if self.started is not None else clock.deactive_img)

        # update labels - if not active - show with milliseconds
        if self.started is not None:
            self.text_lbl.config( text = clock.format_time(self.max_duration - spent))
        else:
            self.text_lbl.config(text = f"Total:\n{clock.format_time(self.total, True)}")

    def start_clock(self):
        # starts the clock
        self.started = datetime.now()
        self.update_lbl(timedelta())

    def tick(self):
        # ticks the clock - stops it if time has run out
        if self.started is not None:
            spent = datetime.now() - self.started
            if spent > self.max_duration:                
                self._stop_clock(spent)
                return False

            self.update_lbl(spent)
            return True
        return None

    def stop_clock(self):
        # stop clock from the outside if <space> is hit
        if self.started is not None:
            spent = datetime.now() - self.started
            self._stop_clock(spent)

    def _stop_clock(self, spent):
        # internal method that stops the clock, adds total & updates
        spent = min(spent, self.max_duration) # fix it
        self.total += spent
        self.started = None
        self.update_lbl(None)

class root(Tk):
    def __init__(self):
        super(root, self).__init__()

        self.title("Chess Clock")
        self.minsize(1539,600)
        self.windowBG = '#313131'
        self.state('zoomed')
        self.configure(bg=self.windowBG)

        self.CreateWindow()

    def CreateWindow(self):
        self.grey = ImageTk.PhotoImage(Image.open(r"grey.png"))
        self.green = ImageTk.PhotoImage(Image.open(r"green.png"))

        # used to determine player
        self.turn = 0

        # give the clock class the two images to switch 
        # if changing between active/inactive state
        clock.deactive_img = self.grey
        clock.active_img = self.green

        # one clocks UI
        self.white_bg = Label(self, image=self.grey, borderwidth=0)
        self.white_bg.place(relx=.3, rely=.55, anchor="center")
        self.white = Label(self, text='White', bg='#454545', fg='white', font ="Gadugi 40 bold")
        self.white.place(relx=.3, rely=.2, anchor="center")
        self.white_timer = Label(self.white_bg, text="", bg='#454545', fg='white', font ="Gadugi 40 bold")
        self.white_timer.place(relx=.5, rely=.5, anchor="center")

        # seconds clock UI
        self.black_bg = Label(self, image=self.grey, borderwidth=0)
        self.black_bg.place(relx=.7, rely=.55, anchor="center")
        self.black = Label(self, text='Black', bg='#454545', fg='white', font ="Gadugi 40 bold")
        self.black.place(relx=.7, rely=.2, anchor="center")
        self.black_timer = Label(self.black_bg, text="", bg='#454545', fg='white', font ="Gadugi 40 bold")
        self.black_timer.place(relx=.5, rely=.5, anchor="center")

        # provide the background-label and the text label
        # for time and create two clocks for the players
        self.clock1 = clock(1, 5, self.white_bg, self.white_timer)
        self.clock2 = clock(1,5, self.black_bg, self.black_timer)

        # which clock is currently in use?
        self.who_is_it = None
        # handles switching to next players clock
        self.bind('<space>', lambda _: self.next_player())
        self.bind('<Control-Key-q>', lambda _: self.stop())
        # check every 200ms if clocks need to be switched over
        self.after(200, self.do_clock_logic)

    def do_clock_logic(self):
        # do nothing if no clock startet
        # check if clock has run out, then switch to next players clock
        if self.who_is_it is not None: 
            # tick() returns False if the player spent all his time
            # tick() returns True if the player still has time
            # tick() returns None if clock is not yet started
            if self.who_is_it.tick() == False:
                self.next_player()

        # recheck clocks in 200ms
        self.after(200, self.do_clock_logic)

    def stop(self):
        """First Ctrl+q will stop clocks, second will quit."""
        if self.who_is_it is not None:
            self.who_is_it.stop_clock()
            self.who_is_it = None
            self.do_clock_logic = lambda _: None is None
        else:
            self.destroy()

    def next_player(self):
        if self.who_is_it is not None:
            self.who_is_it.stop_clock()

        self.turn += 1
        # player 1 on "odd turns", player 2 on "even turns"
        self.who_is_it = self.clock1 if self.turn % 2 else self.clock2
        self.who_is_it.start_clock()

root=root()
root.mainloop()

to get

运行时的时钟图像

after the first CTRL+q you'll get the results - a second time CTRL+q closes your window:

第一个 ctrl+q 后的图像

This can be better structured regarding UI/logic stuff - but it works as proof of concept.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM