How to fix timer increasing countdown speed with each button press - python

I already read a thread that helped my problem with turning the button[state] to 'normal' and 'disabled'.
I would really like to know and learn if there is a conditional/boolean way to do this.
How, or can you start a timer with a button and not have the timer speed up if a user clicks the start button again?
I tried several times to implement a conditional, checking if the button had been pressed, if the time was less than the original variable amount, etc.
But each time the timer would countdown faster and faster with each click. I feel like there is an issue on my part in understanding the fundamentals of how conditionals/booleans work.
from tkinter import *
root = Tk()
hold_time = 120
timer = False
def start_timer():
global timer
timer = True
hold_timer_start()
start_button['state'] = 'disabled'
def hold_timer_start():
global hold_time
global timer
if timer == True:
hold_time -= 1
hold_timer_label['text'] = hold_time
hold_timer_label.after(1000, hold_timer_start)
if hold_time == 0:
timer = False
def hold_timer_stop():
global hold_time
global timer
hold_time = 120
hold_timer_label['text'] = hold_time
timer = False
start_button['state'] = 'normal'
timer_frame = Frame(root, bg='gray25')
timer_frame.grid(row=0, column=0)
hold_timer_label = Label(timer_frame, text='Hold Time', bg='skyblue')
hold_timer_label.grid(row=0, column=1, sticky='ew', columnspan=2, pady=2)
hold_timer_label = Label(timer_frame, text=hold_time, bg='white')
hold_timer_label.grid(row=1, column=1, sticky='ew', columnspan=2, pady=2)
empty_label = Label(timer_frame, bg='gray25')
empty_label.grid(row=2, column=0)
start_button = Button(timer_frame, text='Start', bg='green', fg='black', command=start_timer)
start_button.grid(row=2, column=1)
stop_button = Button(timer_frame, text='Stop', bg='red', fg='black',command=hold_timer_stop)
stop_button.grid(row=2, column=2)
empty_label = Label(timer_frame, bg='gray25')
empty_label.grid(row=2, column=3)
root.mainloop()
I expect the timer to start counting down once the button is pressed but then do nothing if pressed again while the timer is counting down.
Unless the timer has hit zero or the stop button has been pressed.

You already have a timer flag set. All you need to do is to check it.
def start_timer():
global timer
if not timer:
timer = True
hold_timer_start()
#start_button['state'] = 'disabled'

Related

Tkinter Entry Widget: How to constantly check for activity and delete the field if nothing is typed in a period of time

Trying to write a little program where you type in a Tkinter Entry widget and if you don't type for 5 seconds it deletes everything. The best I can do is have it delete everything on the first key pressed after the five seconds elapses, but I can't figure out how to get it to do it without that extra key press.
import time
from tkinter import *
def click(key):
global click_before_last_click, last_click
# print(key.char)
click_before_last_click = last_click
last_click = time.time()
# print(click_before_last_click)
# print(last_click)
delete_shit()
def disappearing_text_start():
global click_before_last_click, last_click
click_before_last_click = time.time()
last_click = time.time()
entry.delete(1.0, END)
entry.bind("<Key>", click)
def disappearing_text_end():
text_file = open("result.txt", "w")
text_file.write(entry.get(1.0, END))
text_file.close()
entry.delete(1.0, END)
def delete_shit():
if last_click > click_before_last_click + 5:
print("TOO LONG")
entry.delete(1.0, END)
if __name__ == "__main__":
click_before_last_click = time.time()
last_click = time.time()
window = Tk()
window.title("Disappearing Text")
window.config(padx=50, pady=20, bg="#D3D3D3")
title_label = Label(text="Disappearing Text App", fg="black", bg="#D3D3D3", font=("Courier", 24))
title_label.grid(column=1, row=0, columnspan=2)
label = Label(text="Click start to begin, and end to save your text. "
"If you stop typing for 5 seconds, you lose everything.",
bg="#D3D3D3", font=("Courier", 14))
label.grid(column=1, row=1, columnspan=2)
entry = Text(width=100, height=30)
entry.grid(column=1, columnspan=2, row=3)
start_button = Button(text="Start", command=disappearing_text_start)
start_button.grid(column=1, row=4, pady=20)
end_button = Button(text="Save", command=disappearing_text_end)
end_button.grid(column=2, row=4, pady=20)
window.mainloop()
You can use after to delete the characters after the time interval. Each time the user presses a key, delete the old scheduled function and then reschedule it.
Also, FWIW, you've used an index of 1.0 which is invalid. Tkinter will accept it, but an index is a string rather than a floating point number.
Let's start by writing a function that will schedule the text to be deleted in 5 seconds. It will also cancel any pending job, effectively resetting the timer to zero. It needs to accept an event parameter since it will be called from a key binding.
after_id = None
def schedule_delete(event=None):
global after_id
if after_id:
window.after_cancel(after_id)
after_id = window.after(5000, delete_shit)
Next, arrange for this to be called when the user clicks the "start" button. disappearing_text_start might look something like this:
def disappearing_text_start():
schedule_delete()
entry.delete("1.0", END)
You can then call this bind command once in the main body of of your program to have reschedule_delete called on every keypress:
entry.bind("<Any-KeyPress>", reschedule_delete)
Finally, we need to cancel any pending job when the user clicks the "stop" button:
def disappearing_text_end():
global after_id
if after_id:
window.after_cancel(after_id)
after_id = None
... the rest of your code here ...
Here's a complete working example:
from tkinter import *
def click(key):
schedule_delete()
def disappearing_text_start():
schedule_delete()
entry.delete("1.0", END)
def schedule_delete(event=None):
global after_id
if after_id:
window.after_cancel(after_id)
after_id = window.after(5000, delete_shit)
def disappearing_text_end():
global after_id
if after_id:
window.after_cancel(after_id)
after_id = None
text_file = open("result.txt", "w")
text_file.write(entry.get(1.0, END))
text_file.close()
entry.delete("1.0", END)
def delete_shit():
entry.delete("1.0", END)
if __name__ == "__main__":
# this is used to keep track of the scheduled function call
after_id = None
window = Tk()
window.title("Disappearing Text")
window.config(padx=50, pady=20, bg="#D3D3D3")
title_label = Label(text="Disappearing Text App", fg="black", bg="#D3D3D3", font=("Courier", 24))
title_label.grid(column=1, row=0, columnspan=2)
label = Label(text="Click start to begin, and end to save your text. "
"If you stop typing for 5 seconds, you lose everything.",
bg="#D3D3D3", font=("Courier", 14))
label.grid(column=1, row=1, columnspan=2)
entry = Text(width=100, height=30)
entry.grid(column=1, columnspan=2, row=3)
start_button = Button(text="Start", command=disappearing_text_start)
start_button.grid(column=1, row=4, pady=20)
end_button = Button(text="Save", command=disappearing_text_end)
end_button.grid(column=2, row=4, pady=20)
entry.bind("<Any-KeyPress>", schedule_delete)
window.mainloop()

How to control processes using Tkinter?

I want to use the tkinter to build a GUI to control the python script.
The code looks like,
kansai = Page(kansai_url)
tokyo = Page(tokyo_url)
def loop_main():
with concurrent.futures.ProcessPoolExecutor() as executor:
k = executor.submit(kansai.compare)
t = executor.submit(tokyo.compare)
kansai_lbl['text'] = k.result()
tokyo_lbl['text'] = t.result()
root.after(60000, loop_main)
if __name__ == '__main__':
root = tk.Tk()
# --buttons--
start_btn = tk.Button(root, text='Start', command=loop_main, font='Raleway', bg='#20bebe', fg='white', height=2,
width=10)
start_btn.grid(column=1, row=3)
refresh_btn = tk.Button(root, text='Refresh', font='Raleway', bg='#20bebe', fg='white', height=2, width=10)
refresh_btn.grid(column=2, row=3)
quit_btn = tk.Button(root, text='Quit', command=root.destroy, font='Raleway', bg='#20bebe', fg='white', height=2,
width=10)
quit_btn.grid(column=3, row=3)
# -- instruction --
kansai_name_lbl = tk.Label(root, text='Kansai', font='Raleway')
kansai_name_lbl.grid(column=1, row=0)
tokyo_name_lbl = tk.Label(root, text='Tokyo', font='Raleway')
tokyo_name_lbl.grid(column=3, row=0)
kansai_lbl = tk.Label(root)
kansai_lbl.grid(column=1, row=1)
tokyo_lbl = tk.Label(root)
tokyo_lbl.grid(column=3, row=1)
root.mainloop()
My goal is that, I want to control the running of the script via the start and stop buttons. The script is written as the loop_main running with multiprocessing, takes about 20secs to finish.
My problem is when I click start, the script started but the GUI just went no responding and I can't click the quit button. Only during the interval of running, I can click the buttons. But I want to exit the script via quit button at any time.
How can I fix this?
I had an issue with tkinter gui becoming unresponsive while application was executing a function. For me the solution was "threading":
import tkinter
import time
from threading import Thread
def start():
def something_slow():
global stop
stop = False
while not stop:
print("doing stuff")
time.sleep(1)
print("stoped doing stuff")
executing = Thread(target=something_slow)
executing.start()
def stop():
global stop
stop = True
main_window_of_gui = tkinter.Tk()
button_start = tkinter.Button(main_window_of_gui, text="Start", command=start)
button_start.grid(row=0, column=0)
button_stop = tkinter.Button(main_window_of_gui, text="Stop", command=stop)
button_stop.grid(row=0, column=1)
main_window_of_gui.mainloop()
stop = True

Can't stop timer in python (after and after_cancel)

I am trying to create a timer in a tkinter GUI that initiates on a start button, and stops on a stop button. I am using .after to loop the timer, but I can't correctly integrate .after_cancel. Any advice would be greatly appreciated.
#import python modules
import time
import math
from tkinter import *
#Create a root window to work in
root = Tk()
root.title("CNT Synthesis Controller") #title of file
#Create global variables to be stored
global var_status
global current_time
global start_time
current_time = 0
#Create Labels
myLabel_Status_T = Label(root, text="Total time elapsed (hrs:min:sec):")
myLabel_Timer = Label(root, text="00:00:00")
#Locate labels
myLabel_Status_T.grid(row=0, column=0)
myLabel_Timer.grid(row=1, column=0)
#Start button function
def Click_Start():
#disable Start Button to prevent re-start
myButton_Start.config(state=DISABLED)
#initiate time = 0 and start timer
start_time = time.time()
global Timer_continue
Timer_continue = True
#Timer function
def Timer():
#determine the amount of time passed since start_time measured
current_time = int(time.time()-start_time)
hour = math.floor(current_time/3600)
minute = math.floor((current_time/60)-(hour*60))
second = math.floor(current_time-(hour*3600)-(minute*60))
#shows time as 00:00:00, i.e. adding in the zeroes where needed
if hour<10:
hour=str("0"+str(hour))
else:
hour=str(hour)
if minute<10:
minute=str("0"+str(minute))
else:
minute=str(minute)
if second<10:
second=str("0"+str(second))
else:
second=str(second)
#print the time to the label myLabel_Timer
myLabel_Timer.config(text= hour + ":" + minute + ":" + second)
#after 1000 ms repeat the Timer function
#while (myButton_Stop['state'] != tk.DISABLED):
#Timer_Object=myLabel_Timer.after(1000,Timer)
if Timer_continue == True:
root.after(1000,Timer)
if Timer_continue == False:
root.after_cancel(Timer)
Timer()
#Stop button function
def Click_Stop():
Timer_continue = False
#disable Stop Button to prevent re-use until program is reset
myButton_Stop.config(state=DISABLED)
#Create Buttons
myButton_Start = Button(root,text="Start CNT Synthesis", padx=40, pady=20, fg="white", bg="green", command=Click_Start)
myButton_Stop = Button(root,text="Stop CNT Synthesis", padx=40, pady=20, fg="white", bg="red", command=Click_Stop)
#Locate buttons
myButton_Start.grid(row=2, column=0)
myButton_Stop.grid(row=2, column=1)
root.mainloop()
####---------------------------------------------------------------------Required added text to make the question 'long enough'--------------------------------------------------------------------------------------------##########
You need to save the ID returned by .after() and use it in .after_cancel() to stop the timer inside Click_Stop() function:
#import python modules
import time
from tkinter import *
#Create a root window to work in
root = Tk()
root.title("CNT Synthesis Controller") #title of file
#Create Labels
myLabel_Status_T = Label(root, text="Total time elapsed (hrs:min:sec):")
myLabel_Timer = Label(root, text="00:00:00")
#Locate labels
myLabel_Status_T.grid(row=0, column=0)
myLabel_Timer.grid(row=1, column=0)
Timer_id = None # used to store the ID returned by .after()
#Start button function
def Click_Start():
#disable Start Button to prevent re-start
myButton_Start.config(state=DISABLED)
#enable Stop Button
myButton_Stop.config(state=NORMAL)
#save the start timer
start_time = time.time()
#Timer function
def Timer():
global Timer_id
#determine the amount of time passed since start_time measured
elapsed = int(time.time()-start_time)
minutes, seconds = divmod(elapsed, 60)
hours, minutes = divmod(minutes, 60)
myLabel_Timer.config(text=f"{hours:02}:{minutes:02}:{seconds:02}")
#after 1000 ms repeat the Timer function
Timer_id = root.after(1000,Timer) # save the after ID
Timer()
#Stop button function
def Click_Stop():
if Timer_id:
root.after_cancel(Timer_id) # cancel the scheduled task
#disable Stop Button to prevent re-use until program is reset
myButton_Stop.config(state=DISABLED)
#enable Start Button
myButton_Start.config(state=NORMAL)
#Create Buttons
myButton_Start = Button(root,text="Start CNT Synthesis", padx=40, pady=20,
fg="white", bg="green", command=Click_Start)
myButton_Stop = Button(root,text="Stop CNT Synthesis", padx=40, pady=20,
fg="white", bg="red", command=Click_Stop, state=DISABLED) # disable stop button initially
#Locate buttons
myButton_Start.grid(row=2, column=0)
myButton_Stop.grid(row=2, column=1)
root.mainloop()
Miss the global statement:
def Click_Stop():
global Timer_continue
Timer_continue = False

Tkinter buttons not showing on different screen

It works on my monitor (1920x1080) perfectly but when I use my laptop (Also 1920x1080) none of the buttons or the timer shows..... I've tried using 2 different geometry managers to manage the placement of elements. At first I used .place() but that wasn't working/ideal on my laptop so I switched to .grid(). .grid() was working perfectly on my laptop until today. How can I fix this?
Code:
# Required libraries.
# This is for the gui.
import tkinter as tk
# This is for the timer.
from datetime import datetime
# Sets the Timer up.
counter = 66600
running = False
# Defines the timer.
def counter_label(label):
def count():
if running:
global counter
# To manage the initial delay.
if counter==66600:
display="Starting..."
else:
tt = datetime.fromtimestamp(counter)
string = tt.strftime("%H:%M:%S")
display=string
label['text']=display
label.after(1000, count)
counter += 1
# This starts the timer.
count()
# Start function of timer.
def Start(label):
global running
running=True
counter_label(label)
start['state']='disabled'
stop['state']='normal'
reset['state']='normal'
# Stop function of the timer.
def Stop():
global running
start['state']='normal'
stop['state'] ='disabled'
reset['state']='normal'
running = False
# Reset function of the timer.
def Reset(label):
global counter
# Sets the timer to 0.
counter=10800
# If reset is pressed after pressing stop.
if running==False:
reset['state']='disabled'
# This displays CBR when the timer is not in use.
label['text']='CBR'
# If reset is pressed while stopwatch is running.
else:
label['text']='Starting...'
# Declares the gui.
gui = tk.Tk()
# Changes what the gui is called.
gui.title('Car Go Brrrr v1.9')
# Sets Windows size.
gui.geometry("1920x1080")
# Columns
gui.columnconfigure(0, weight=1)
gui.rowconfigure(0, weight=1)
# This gets grid() to work.
for i in range(20):
tk.Frame(gui, width=20, height=20).grid(row=0, column=i)
for j in range(20):
tk.Frame(gui, width=50, height=50).grid(column=0, row=j)
# Pitstop Button
pitButton = tk.Button(gui, text="Call Pitstop")
pitButton.grid(column=18, row=17)
pitButton.config(width=11, height=5)
# Defines the Exit telemetry button.
def clickExitButton():
exit()
# Exit button code.
exButton = tk.Button(gui, text="Exit Telemetry", command=clickExitButton)
exButton.grid(column=18, row=18)
exButton.config(width=11, height=1)
# Timer label text.
label = tk.Label(text="CBR", fg="black", font="Verdana 30 bold")
label.grid(column=18, row=0)
# Start button.
start = tk.Button(text="Start Timer", command=lambda:Start(label))
start.config(width=11, height=1)
start.grid(column=18, row=16)
# Stop button.
stop = tk.Button(text="Stop Timer", state='disabled', command=Stop)
stop.config(width=11, height=1)
stop.grid(column=18, row=15)
# Reset button.
reset = tk.Button(text="Reset Timer", state='disabled', command=lambda:Reset(label))
reset.config(width=11, height=1)
reset.grid(column=18, row=14)
# Never ever ever remove this, this is essential for the gui to work.
gui.mainloop()
Thanks!

How to display the value of function in tkinter?

I am new to python and tkinter and I have decided that I will make a stopwatch.
I have gooled alot and find many useful information, but I still haven't found how to display value of a function in tkinter. Here is my current code:
import time
from tkinter import*
import os
root = Tk()
def clock(event):
second = 0
minute = 0
hour = 0
while True:
time.sleep(0.99)
second +=1
print(hour,":",minute,":",second)
return
def stop(event):
time.sleep(1500)
def clear(event):
os.system('cls')
button1 = Button(root, text="Start")
button2 = Button(root, text="Stop")
button3 = Button(root, text="Clear")
button1.bind("<Button-1>", clock)
button2.bind("<Button-1>", stop)
button3.bind("<Button-1>", clear)
button1.grid(row=2, column=0, columnspan=2)
button2.grid(row=2, column=2, columnspan=2)
button3.grid(row=2, column=4, columnspan=2)
root.mainloop()
I am aware that the code isn't perefect yet(especially the functions stop and clear).
You might consider using callback functions (i.e. call to your function when something happens — when clicking a button for example):
Quoting portions of Tkinter Callbacks:
In Tkinter, a callback is Python code that is called by Tk when
something happens. For example, the Button widget provides a command
callback which is called when the user clicks the button. You also use
callbacks with event bindings.
You can use any callable Python object as a callback. This includes
ordinary functions, bound methods, lambda expressions, and callable
objects. This document discusses each of these alternatives briefly.
...
To use a function object as a callback, pass it directly to Tkinter.
from Tkinter import *
def callback():
print "clicked!"
b = Button(text="click me", command=callback)
b.pack()
mainloop()
It's unclear from your sample code which function's value you want to display.
Regardless, a good way to do accomplish something like that in tkinter is by creating instances of its StringVar Variable class and then specifying them as the textvariable option of another widget. After this is done, any changes to the value of the StringVar instance will automatically update the associated widget's text.
The code below illustrates this:
import os
import time
import tkinter as tk
class TimerApp(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master=None)
self.grid()
self.create_widgets()
self.elapsed = 0
self.refresh_timer()
self.after_id = None # used to determine and control if timer is running
def create_widgets(self):
self.timer = tk.StringVar()
self.timer.set('')
self.timer_label = tk.Label(self, textvariable=self.timer)
self.timer_label.grid(row=1, column=2)
self.button1 = tk.Button(self, text="Start", command=self.start_clock)
self.button1.grid(row=2, column=0, columnspan=2)
self.button2 = tk.Button(self, text="Stop", command=self.stop_clock)
self.button2.grid(row=2, column=2, columnspan=2)
self.button3 = tk.Button(self, text="Clear", command=self.clear_clock)
self.button3.grid(row=2, column=4, columnspan=2)
def start_clock(self):
self.start_time = time.time()
self.after_id = self.after(1000, self.update_clock)
def stop_clock(self):
if self.after_id:
self.after_cancel(self.after_id)
self.after_id = None
def clear_clock(self):
was_running = True if self.after_id else False
self.stop_clock()
self.elapsed = 0
self.refresh_timer()
if was_running:
self.start_clock()
def update_clock(self):
if self.after_id:
now = time.time()
delta_time = round(now - self.start_time)
self.start_time = now
self.elapsed += delta_time
self.refresh_timer()
self.after_id = self.after(1000, self.update_clock) # keep updating
def refresh_timer(self):
hours, remainder = divmod(self.elapsed, 3600)
minutes, seconds = divmod(remainder, 60)
self.timer.set('{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds))
app = TimerApp()
app.master.title('Timer')
app.mainloop()

Categories

Resources