Posts: 2
Threads: 1
Joined: Oct 2024
Oct-12-2024, 10:43 PM
(This post was last modified: Oct-12-2024, 10:43 PM by Mike15.)
I need help getting my timer to work.
This timer, when started, will have a decreasing time of 2 minutes, and when it reaches time 00:01 it will automatically return to 2 minutes again, working in loop mode every time it restarts it starts automatically, but at time 02 :00 to 01:59 it will only take 0.5 milliseconds from 01:59 to 00:01 it will decrease the time by 1 second normally.
It is not necessary for the timer to reach 00:00 to restart. This timer rings an alarm in the last 10 seconds, the path is already defined in the script, I just need help to fix the timer part, I'll leave the code I'm using below.
PS: remember that when you press the Break key it resets the count and starts the 2 minutes from the beginning.
import tkinter as tk
import pygame
import os
class TimerApp:
def __init__(self, master):
self.master = master
master.title("Temporizador")
self.timer_duration = 120 # 2 minutos
self.time_left = self.timer_duration
self.timer_running = True # Inicia automaticamente
# Configurar a janela para estar sempre em cima
master.attributes("-topmost", True)
master.geometry("170x90+0+0") # Dimensões fixas
self.label = tk.Label(master, text=self.format_time(self.time_left), font=("Helvetica", 48), fg="red", bg="black") # Fonte aumentada
self.label.pack(pady=20)
self.master.bind('<Pause>', lambda event: self.reset_timer()) # Liga a tecla Pause/Break para reiniciar
# Inicializa o pygame para tocar o som
pygame.mixer.init()
self.alarm_sound = "C:\\Users\\miche\\OneDrive\\Documentos\\Temporizador Tibia Piranha\\Alarm Piranha.MP3"
self.update_timer() # Inicia o loop de atualização do temporizador
def format_time(self, seconds):
minutes = seconds // 60
seconds = seconds % 60
return f"{minutes:02}:{seconds:02}"
def reset_timer(self):
self.time_left = self.timer_duration
self.label.config(text=self.format_time(self.time_left))
pygame.mixer.music.stop() # Para o alarme ao reiniciar
def update_timer(self):
if self.timer_running:
if self.time_left > 0:
if self.time_left <= 10: # Toca o alarme nos últimos 10 segundos
self.play_alarm()
self.time_left -= 1
self.label.config(text=self.format_time(self.time_left))
else: # Quando chega a 0
self.label.config(text=self.format_time(1)) # Mostra 00:01
pygame.mixer.music.stop() # Para o alarme
self.master.after(1000, self.reset_timer) # Aguarda 1 segundo antes de reiniciar
return # Sai para não atualizar mais este ciclo
self.master.after(1000, self.update_timer) # Atualiza a cada segundo
def play_alarm(self):
# Toca o som do alarme
if not pygame.mixer.music.get_busy(): # Toca apenas se não estiver tocando
pygame.mix I'm using the version 3.17.2
Posts: 1,145
Threads: 114
Joined: Sep 2019
Oct-13-2024, 01:11 PM
(This post was last modified: Oct-13-2024, 01:11 PM by menator01.)
Are you trying to do something like this?
import tkinter as tk
class Timer:
''' Timer class creates a timer object '''
def __init__(self, duration=0):
self.duration = duration
def countdown(self):
''' Method for doing the countdown '''
self.duration -= 1
return self.duration
def update(self):
''' Method for formatting and returning timer '''
duration = self.countdown()
if duration <= 0:
self.duration = 0
#format
hours = divmod(self.duration, 3600)
minutes = divmod(hours[1], 60)
seconds = divmod(minutes[1],60)
return f'{hours[0]:02}:{minutes[0]:02}:{seconds[1]:02}'
class Window:
''' Window class is for displaying timer '''
def __init__(self, parent):
self.parent = parent
self.parent.geometry('+300+300')
self.parent.configure(padx=4, pady=4)
header = tk.Label(self.parent, text='Countdown Timer', padx=5, pady=10)
header.pack(side='top', fill='x', pady=5)
header.configure(font=(None, 18, 'bold'), bg='#555555', fg='#ffffff')
label = tk.Label(self.parent, wraplength=300, justify='left', anchor='w')
label.pack(fill='x', expand=True, pady=(5,10), ipadx=5)
label.configure(
text='Timer reaches 0 auto reset, press (r) timer resets',
font = ('tahoma', 12, 'italic'),
highlightcolor = '#555555',
highlightbackground = '#555555',
highlightthickness = 1,
pady=5
)
self.label = tk.Label(self.parent, anchor='w', padx=5, pady=5)
self.label.pack(side='left', fill='x', expand=True)
self.label.configure(
text='Timer: ', font=(None, 14, 'normal'),
highlightcolor ='#555555',
highlightbackground='#555555',
highlightthickness=1
)
class Controller:
''' Controller class handles communication between Timer and Window classes '''
def __init__(self, timer, window):
# Set instance variables
self.timer = timer
self.window = window
# default counter is set to 10 seconds. can be any number
# To set for 2 minutes the number would be 120
# to set for 1 hour the number would be 3600
# To set for 5 hours the number would be 18000
self.default = 10
# Set timer duration to default - This could be any number
# Example self.timer.duration = 120 - Would set to 2 minutes
# You would need to make changes to other places in script.
# Best option is to set default to timer length
self.timer.duration = self.default
self.window.label.configure(text=f'Timer: {self.timer.update()}')
# Bind r key to reset function
self.window.parent.bind('<r>', self.reset)
# Get everything started
self.update()
def update(self):
''' Method updates display window '''
self.window.label.configure(text=f'Timer: {self.timer.update()}')
if self.timer.duration <= 0:
self.timer.duration = self.default
bgcolor = 'gray86'
if self.timer.duration <= 5:
if self.timer.duration % 2 == 0:
bgcolor = 'orange'
elif self.timer.duration % 2 != 0:
bgcolor = 'orangered'
else:
bgcolor = 'gray86'
self.window.label.configure(bg=bgcolor)
self.window.parent.after(1000, self.update)
def reset(self, event=None):
''' Method for resetting timer '''
self.timer.duration = self.default
if __name__ == '__main__':
root = tk.Tk()
controller = Controller(Timer(), Window(root))
root.mainloop()
Posts: 2
Threads: 1
Joined: Oct 2024
yes, that's how I need it, but I also need the timer screen to overlay any screen, for the time to be 02:00 minutes decreasing, and for an alarm to ring in the last few seconds so I know there are 10 seconds left to start again, and finally the last second and the first will only take 0.5 milliseconds to exchange and the rest always 1 second.
in the case of 00:01 to 00:00 it will only take 0.5 milliseconds and from 02:00 to 01:59 it will also take only 0.5 seconds
Posts: 1,145
Threads: 114
Joined: Sep 2019
Oct-14-2024, 04:46 PM
(This post was last modified: Oct-15-2024, 01:06 PM by menator01.)
This is about as close as I can get to what you want.
Splitting a second between the numbers you want, don't think is possible
Read the comments in the code to see how everything works
If the pause key doesn't reset the counter, just click on the counter window to reset focus.
Updated key binds
shift+r = reset
pause = pause/resume counter
esc = exit program
Changed code a little
added self.parent.wait_visibility(self.parent above self.parent.wm_attributes('-alpha', 0.5)
This changes opacity 0.0 being not visible and 1.0 being fully visible
Added check for sound file. If one is not found won't error. Uses visual only
Update:
Added class for dragging widget around on desktop
import tkinter as tk
import pygame
from pathlib import Path
from os.path import exists
# Get path to executing script
path = Path(__file__).parent
# Initialize pygame.mixer
pygame.mixer.init()
class Sound:
''' Sound class loads, plays, and stops sound file '''
def __init__(self, audio=''):
self.audio = audio
self.verify = False
if self.audio and exists(f'{path}/{self.audio}'):
pygame.mixer.music.load(f'{path}/{self.audio}')
self.verify = True
def play(self):
''' Method for playing sound file '''
if self.verify:
pygame.mixer.music.play()
def stop(self):
''' Method to stop playing sound file '''
if self.verify:
pygame.mixer.music.stop()
class DragIt:
''' DragIt class takes a widget and moves it around the desktop '''
def __init__(self, widget):
self.widget = widget
# Mouse binds
widget.bind('<1>', self.grab)
widget.bind('<B1-Motion>', self.move)
widget.configure(cursor='hand1')
widget.bind('<ButtonRelease>', self.reset)
def grab(self, event):
''' Method for getting start position '''
self.widget.configure(cursor='hand2')
self.start_x = event.x
self.start_y = event.y
def move(self, event):
''' Method for moving widget '''
dx = event.x - self.start_x
dy = event.y - self.start_y
left = self.widget.winfo_x() + dx
top = self.widget.winfo_y() + dy
self.widget.geometry(f'+{left}+{top}')
def reset(self, event):
''' Method resets cursor pointer '''
self.widget.configure(cursor='hand1')
class Timer:
''' Timer class sets a countdown timer - default is 2 minutes '''
def __init__(self, duration=120):
self.duration = duration+1
def update(self):
''' Method for updating count '''
self.duration -= 1
hours = divmod(self.duration, 3600)
minutes = divmod(hours[1], 60)
seconds = divmod(minutes[1], 60)
return f'{hours[0]:02}:{minutes[0]:02}:{seconds[1]:02}'
class Window:
''' Window class is for displaying window '''
def __init__(self, parent):
parent.geometry('+50+50')
parent.minsize(130,30)
parent.maxsize(130,30)
parent.wait_visibility(parent)
parent.wm_attributes('-topmost', True)
parent.wm_attributes('-alpha', 0.5)
parent.wm_attributes('-type', 'splash')
parent.focus_force()
self.parent = parent
self.label = tk.Label(parent, anchor='w', padx=10)
self.label.pack(fill='x', expand=True)
self.label.configure(font=(None, 18, 'normal'))
class Controller:
''' Controller class handles communications between Timer and Window class '''
def __init__(self, window, timer):
# Create instance variables
self.window = window
self.timer = timer
self.action = False
self.duration = self.timer.duration
# Create the alarm sound
# Path to sound file example media/myalarm.mp3
# If in the same directory as script, just filename.mp3 will work
# This can also be empty for no sound or if file not found
# should still work
self.alarm = Sound()
# Make the window draggable
widget = DragIt(self.window.parent)
# Key binds
self.window.parent.bind('<R>', self.reset)
self.window.parent.bind('<Pause>', self.pause)
self.window.parent.bind('<Escape>', lambda event: self.window.parent.destroy())
# Get it started
self.update()
def update(self):
''' Method updates thw window '''
self.window.label.configure(text=self.timer.update())
# If counter is 10 or below, play alarm and change bgcolor
if self.timer.duration <= 10:
self.alarm.play()
if self.timer.duration % 2 == 0:
self.window.label.configure(bg='red', fg='white')
else:
self.window.label.configure(bg='orange', fg='red')
# If timer reaches 0 - reset
# I tried calling the reset function here but, caused problems with counter
if self.timer.duration <= 0:
self.timer.duration = self.duration
self.alarm.stop()
self.window.label.configure(bg='gray86', fg='black')
# Call .after to update window
self.updating = self.window.parent.after(1000, self.update)
def reset(self, event):
''' Method for resetting everything - key bind is shift+r '''
self.window.parent.after_cancel(self.updating)
self.timer.duration = self.duration
self.action = False
self.window.label.configure(bg='gray86', fg='black')
self.alarm.stop()
self.update()
def pause(self, event):
''' Method for pausing counter '''
self.action = True if not self.action else False
if self.action:
self.window.parent.after_cancel(self.updating)
self.timer.duration = self.timer.duration
self.alarm.stop()
else:
self.update()
if __name__ == '__main__':
root = tk.Tk()
# Timer excepts milaseconds - example 120 = 2 minutes (60x2 = 120 milaseconds = 2 minutes)
# If Timer left blank will default to 2 minutes
controller = Controller(Window(root), Timer(15))
root.mainloop()
Posts: 6,815
Threads: 20
Joined: Feb 2020
Oct-14-2024, 08:40 PM
(This post was last modified: Oct-14-2024, 08:40 PM by deanhystad.)
Using .after() to keep track of time is a bad idea. .after() is really after, as in "After the time has passed I'll get around to calling your function." If accurate reporting is important, you should record the start time and use that when computing time remaining.
I would call the after function 100 times a second. 10ms or 0.5ms is imperceptible, and there is no guarantee when the time display is repainted anyway. There will also be a lot of variability in the time between pressing Pause and mainloop calling the reset_timer() method. asking for 0.5ms timer resolution is silly in this context. I take back what I said about 100 times a second. I would call the after function 20 times a second and know there is no difference in anything I can see between that and updating 2000 times a second (0.5ms resolution).
import time
import tkinter as tk
from pathlib import Path
from pygame import mixer
class TimerApp(tk.Tk):
def __init__(self, duration=120, alarm_start=10, alarm=None, width=8):
super().__init__()
self.duration = duration
self.alarm_start = alarm_start
self.alarm = alarm
self.title("Temporizador")
self.attributes("-topmost", True)
self.bind("<Pause>", lambda event: self.reset_timer())
self.timestr = tk.StringVar(self, "")
tk.Label(self, textvariable=self.timestr, font=("Helvetica", 48), fg="red", bg="black", width=width).pack(
pady=20
)
if self.alarm:
mixer.init()
mixer.music.load(self.alarm)
self.reset_timer()
def reset_timer(self):
mixer.music.stop()
self.play_alarm = False
self.start_time = time.time()
self.update_timer()
def update_timer(self):
remaining_time = self.duration + self.start_time - time.time()
if remaining_time > 0:
if self.alarm and not self.play_alarm and remaining_time <= self.alarm_start:
self.play_alarm = True
mixer.music.play()
self.update_display(remaining_time)
self.after(10, self.update_timer)
else:
self.update_display(self.duration)
if self.play_alarm:
mixer.music.stop()
def update_display(self, seconds):
if self.duration > 60:
minutes, remainder = divmod(int(seconds + 0.999), 60)
self.timestr.set(f"{minutes}:{remainder:>02}")
else:
self.timestr.set(f"{int(seconds+0.999)}")
TimerApp(duration=10, alarm_start=5, alarm=Path(__file__).parent / "alarm.mp3").mainloop() Use default arguments instead of hard coded constants. Avoid absolute paths for support files
|