How do I stop this timer when it reaches a number? - python

import tkinter as tk
counter = 0
def counter_label(label):
counter = 0
def count():
global counter
counter += 1
label.config(text=str(counter))
label.after(10, count)
count()
root = tk.Tk()
root.title("counter")
label= tk.Label(root, fg = "dark green")
label.pack()
counter_label(label)
button = tk.Button(root, text="stop", width=40, command = root.destroy)
button.pack()
root.mainloop()

You can do it by not scheduling the count() function to be called again (via the after() method) after the counter has reached or exceeded a specified limit value.
You also don't need to use a global variable. An advantage of that being that it would allow the same function to be applied to than one Label at a time — each using its own counter. Note I also added an optional rate argument which controls how much the counter is incremented each iteration.
import tkinter as tk
def counter_label(label, limit, rate=1):
"""Update counter display on label at given rate until it reaches limit."""
counter = 0
def count():
nonlocal counter
counter += rate
label.config(text=str(counter))
if counter < limit:
label.after(10, count)
count() # Start counting.
root = tk.Tk()
root.title("counter")
label= tk.Label(root, fg="dark green")
label.pack()
counter_label(label, 500)
button = tk.Button(root, text="stop", width=40, command=root.destroy)
button.pack()
root.mainloop()
Generalizing
Here's a more generic version of the counter_label() function that avoids the use of hardcoded quantities and calculates what it needs from the arguments passed to it.
import tkinter as tk
def counter_label(label, limit, duration, updates_per_sec):
""" Update counter display on label over time period at given updates per
second until it reaches limit.
"""
incr_per_sec = limit / duration
incr_per_call = incr_per_sec / updates_per_sec
ms_delay = int(1/updates_per_sec * 1000)
counter = 0
def count():
nonlocal counter
counter += incr_per_call
label.config(text=format(counter, '.3f'))
if counter < limit:
label.after(ms_delay, count)
count() # Start counting.
root = tk.Tk()
root.title("counter")
label= tk.Label(root, fg="dark green")
label.pack()
counter_label(label, 1000, 5, 20)
button = tk.Button(root, text="stop", width=40, command=root.destroy)
button.pack()
root.mainloop()

Related

Python: stop one incidence of recursive function and run another without resuming the first

I am trying to make a game using a countdown timer for 60s.
My issue is that as the countdown timer is recursive, it keeps running until t == 0 when it runs the endgame() function.
if you pass the level, the initial countdown timer will still end after 60s which in turn will end the game. I need a way to run the next level without the initial countdown ending and ending the game.
I have tried using if True statements at the start of the function which only paused the loop until the next instance of the function started.
code:
from tkinter import *
import ctypes
from scrambld_back import *
from tkinter import font
import time
from nltk.corpus import words
user32 = ctypes.windll.user32
screensize = [user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)]
root = Tk()
root.geometry(f'500x500+{screensize[1]-250}+100')
root.title('Scrambld')
root.configure(bg='white')
over = Tk()
over.geometry(f'500x500+{screensize[1]-250}+100')
root.title('Scrambld')
root.configure(bg='white')
over.iconify()
gamefont = font.Font(family='impact', size=30)
levelfont = font.Font(family='impact', size=20)
level = 0
Label(over, text='GAME OVER', font=gamefont)
Label(over, text=f'Level {level}', font=levelfont)
def endgame():
over = Toplevel(root)
over.geometry(f'500x500+{screensize[1]-250}+100')
Label(over, text='GAME OVER', font=gamefont).place(x=250, y=215, anchor='center')
Label(over, text=f'Level {level}', font=levelfont).place(x=250, y=285, anchor='center')
def play(level):
t = 15
gamewords = []
for x in words.words():
if len(x) == level+3:
gamewords.append(x)
word = gamewords[random.randint(0, len(gamewords))]
gameplay = generate(list(word))
Label(root, text=f'Level {level}', font=levelfont, bg='white').place(x=250, y=70, anchor='center')
Label(root, text=gameplay, font=gamefont, bg='white', width=100).place(x=250, y=140, anchor='center')
guess = Entry(root, font=levelfont, bg='white')
guess.place(x=250, y=360, anchor='center')
guess.bind('<Return>', lambda event, word=word: compare(event, word))
def compare(event, word):
if word.upper() == guess.get().upper():
play(level+1)
else:
pass
submit = Button(root, text='SUBMIT', font=levelfont, bg='white', width=21, command=lambda: compare(None, word))
submit.place(x=250, y=420, anchor='center')
timer = StringVar()
Label(root, textvariable=timer, font=levelfont, bg='white', width=8).place(x=250, y=250, anchor='center')
def countdown(t, lev):
print(lev, level)
print(t)
t=int(t)-1
if t < 10:
t=f'0{t}'
timer.set(f'00:{t}')
root.update()
if int(t) < 1 and lev == level:
endgame()
else:
root.after(1000, lambda: countdown(t, lev))
countdown(t, 1)
play(1)

Python Serial Read Timer

So Im reading a serial device that sends data every couple of seconds, I have my timer running on the screen and the goal is that every time it updates the serial read it restarts the timer to zero.. This allows me to visually see if Ive lost contact with the device. Ive spent alot of time searching to no avail. Not real good with python so any help would be appreciated.
from tkinter import *
from serial import Serial
import time
from serial.threaded import ReaderThread, Protocol
import threading
import tkinter as tk
#Input ED Number
ed_num = 0
def tkinter_input(prompt=""):
global ed_num
root = tk.Tk()
root.wm_attributes('-fullscreen',1)
root.configure(background='black')
tk.Label(root, font=('Ariel', 100), foreground='Green',
background='Black', text=prompt).pack()
entry = tk.Entry(root, font=('Ariel', 100),justify='center', foreground='Black',
background='white')
entry.focus()
entry.pack()
result = None
def callback(event):
nonlocal result
result = entry.get()
root.destroy()
entry.bind("<Return>", callback)
root.mainloop()
return result
result = tkinter_input("Scan Dosimeter")
ed_num = result
print(ed_num)
#Start Loop
root = Tk()
#Sets Main Window
root.wm_attributes('-fullscreen',1)
root.configure(background='black')
'''Shuts Down App will need to remove'''
button_quit = Button(root, text = 'QUIT', command = root.destroy).pack(anchor=NE)
#Container Frame
mainframe= Frame(root, bd=4, bg='Black')
mainframe.pack(anchor=CENTER, expand=True, fill=BOTH, padx=(20,20), pady=(20,20))
mainframe.grid_columnconfigure(0, weight=1)
mainframe.grid_rowconfigure(0, weight=1)
mainframe.grid_rowconfigure(1, weight=1)
mainframe.grid_rowconfigure(2, weight=1)
#ID Label
ed_id_label = Label(mainframe,
font=('Ariel', 50), foreground='Green',
background='Black', anchor='w')
ed_id_label.grid(row=0, column=0, sticky='ew', padx=(100,100), pady=(100,100))
#Dose Label
doselabel = Label(mainframe,
font=('Ariel', 130), foreground='Green',
background='Black', anchor='w')
doselabel.grid(row=1, column=0, sticky='ew', padx=(100,100), pady=(100,100))
# Rate Label
ratelabel = Label(mainframe,
font=('Ariel', 130), foreground='Green',
background='Black', anchor='w')
ratelabel.grid(row=2, column=0, sticky='ew', padx=(100,100), pady=(100,100))
#Timer Label
timelabel = Label(mainframe,
font=('Ariel', 20), foreground='Green',
background='Black', anchor='w')
timelabel.grid(row=3, column=0, sticky='se')
#Data Timer
seconds = 0
def countup(seconds):
seconds += 1
timelabel['text'] = str(seconds)+"s"
root.after(1000, countup, seconds)
#Serial Reader Thread
class SerialReaderProtocolRaw(Protocol):
def data_received(self, data):
"""Called with snippets received from the serial port"""
updateLabelData(data)
def updateLabelData(data):
data = data.decode("utf-8")
if data[0:6] == ed_num:
ed_id_label['text']='Ed id: ' +data[0:6]
doselabel['text']='Dose: ' + data[11:14]+'.'+data[14:15]
if data[21:22] == '0':
ratelabel['text']='Rate: ' + data[18:19]
if data[21:22] == '1':
ratelabel['text']='Rate: ' + data[18:20]
if data[21:22] == '2':
ratelabel['text']='Rate: ' + data[18:21]
if data[21:22] == '3':
ratelabel['text']='Rate: ' + data[18:22]
if data[6:7] =='1':
doselabel.config(bg= "red")
if data[6:7] =='2':
ratelabel.config(bg= "red")
root.update_idletasks()
# Initiate serial port
serial_port = Serial('COM3', baudrate = 57600)
# Initiate ReaderThread
reader = ReaderThread(serial_port, SerialReaderProtocolRaw)
# Initiate Counter Thread
countupthread = threading.Thread(target=countup, args = (seconds,))
# Start reader/counter
countupthread.start()
reader.start()
# Join Threads
countupthread.join()
root.after(1, countup, seconds)
root.mainloop()
There are at least two solutions I can think of off the top of my head: cancel and restart the counter, or make seconds global.
Stop and restart the counter
When you schedule something with after, it returns an identifier. You can pass this identifier to after_cancel to prevent that function from running. Since you're not using classes, you can use a global variable to track this identifier.
Example:
def countup(seconds):
global after_id
seconds += 1
timelabel['text'] = str(seconds)+"s"
after_id = root.after(1000, countup, seconds)
Then, whenever you need to reset the timer you can use the following two lines to cancel and restart countup:
root.after_cancel(after_id)
countup(0)
Make seconds global
Instead of passing seconds to the countup function, have it use a global variable. You can then just reset this variable whenever you want.
seconds = 0
def countup():
global seconds
seconds += 1
timelabel['text'] = str(seconds)+"s"
after_id = root.after(1000, countup, seconds)
With that, whenever you want to reset the counter you simply need to reset seconds:
global seconds
seconds = 0

Tkinter after() method only executes once

After executing insertion() for the first time (i=0), it waits the 1000 ms but after it instantly inserts 'asd' into the other entry widgets.
How could I modify the program so that it waits 1000 ms each time before inserting into the other widgets?
My code:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, height=600, width=540, bg='#F0F0F0')
canvas.pack()
entry = tk.Entry(bg='white', bd=0, width=1, justify='center')
entry2 = tk.Entry(bg='white', bd=0, width=1, justify='center')
entry3 = tk.Entry(bg='white', bd=0, width=1, justify='center')
canvas.create_window(30, 30, window=entry, height=50, width=50)
canvas.create_window(90, 30, window=entry2, height=50, width=50)
canvas.create_window(150, 30, window=entry3, height=50, width=50)
def insertion(entry_list, i):
entry_list[i].after(1000, lambda: entry_list[i].insert(0, 'asd'))
entry_list = [entry, entry2, entry3]
for i in range(len(entry_list)):
insertion(entry_list, i)
root.mainloop()
There are 2 ways I would proceed here, one is as jasonharper mentioned in the comments, to increment the time over each iteration, but that just a hacky way around. I would say to get rid of the loop and then call the function each time using after(). Here is how I think it should be:
count = 0 # Initial index number
def insertion(count):
entry_list[count].insert(0, 'asd')
count += 1 # Increase the index number
rep = root.after(1000,insertion,count) # Pass the new index number
if count >= len(entry_list): # If count is more than number of items in
list,
root.after_cancel(rep) # Then stop repeating the function
root.after(1000,insertion,count) # Wait 1 second before inserting into first box
entry_list = [entry, entry2, entry3]
foo()
Now there will be 1 second delay each time the function is called.
If your wondering on how more you can shorten your code, then take a look here:
import tkinter as tk
from tkinter import font
root = tk.Tk()
count = 0
def insertion(count):
entry_list[count].insert(0, 'asd')
count += 1
rep = root.after(1000,insertion,count)
if count >= len(entry_list):
root.after_cancel(rep)
canvas = tk.Canvas(root, height=600, width=540, bg='#F0F0F0')
canvas.pack()
entry_list = []
x,y = 30,30 # x and y coordinates
TOTAL_ENTRY_NUMBER = 3
for i in range(TOTAL_ENTRY_NUMBER):
entry_list.append(tk.Entry(bg='white', bd=0, width=1, justify='center'))
canvas.create_window(x*(i+1)*2, y, window=entry_list[i], height=50, width=50) # i+1 because i starts from 0
root.after(1000,insertion,count) # Initial delay call to function
root.mainloop()

how to cancel the after method for an off delay timer in tkinter using python 3.8

I have a off delay program in which when I select the input checkbutton, the output is 1. When I deselect the input checkbutton, the output goes back to 0 after a timer (set up in a scale). For that I use the after method. That part works. My problem is that I want to reset the timer if the checkbutton is selected again before the output went to 0; but once the checkbutton is selected the first time, the after method get triggered and it doesn't stop. I'm trying to use after_cancel, but I can't get it to work. Any solution?
from tkinter import *
root = Tk()
t1= IntVar()
out = Label(root, text="0")
remain_time = IntVar()
grab_time = 1000
def start_timer(set_time):
global grab_time
grab_time = int(set_time) * 1000
def play():
if t1.get() == 1:
button1.configure(bg='red')
out.configure(bg="red", text="1")
else:
button1.configure(bg='green')
def result():
out.configure(bg="green", text="0")
out.after(grab_time,result)
button1 = Checkbutton(root,variable=t1, textvariable=t1, command=play)
time = Scale(root, from_=1, to=10, command=start_timer)
button1.pack()
time.pack()
out.pack()
root.mainloop()
Expected: when press the checkbutton before the output went to 0, reset the counter.
So you could use the .after_cencel when the value of checkbutton is 1:
from tkinter import *
root = Tk()
t1= IntVar()
out = Label(root, text="0")
remain_time = IntVar()
grab_time = 1000
def start_timer(set_time):
global grab_time
grab_time = int(set_time) * 1000
def play():
if t1.get() == 1:
button1.configure(bg='red')
out.configure(bg="red", text="1")
try: # when the first time you start the counter, root.counter didn't exist, use a try..except to catch it.
root.after_cancel(root.counter)
except :
pass
else:
button1.configure(bg='green')
def result():
out.configure(bg="green", text="0")
root.counter = out.after(grab_time,result)
button1 = Checkbutton(root,variable=t1, textvariable=t1, command=play)
time = Scale(root, from_=1, to=10, command=start_timer)
button1.pack()
time.pack()
out.pack()
root.mainloop()

How do I make time.sleep() work with tkinter?

I'm trying to show a sequence of numbers on the screen at regular intervals.
I'm new to python so it may be something obvious but I have tried .after and pygame.time.wait, but neither worked.
this is the code:
from tkinter import*
from random import *
import time
my_list = []
def Create_NUM(event):
x = 0
for x in range(level + 2):
button1.destroy()
num = randint(1, 100)
my_list.append(num)
Label(root, text=num,fg="red").pack()
one.pack()
time.sleep(2)
root=Tk()
num = 0
level = 1
bottomFrame = Frame(root)
bottomFrame.pack(side=BOTTOM)
button1 = Button(bottomFrame, text="Click to start game",fg="red")
button1.bind("<Button-1>", Create_NUM)
button1.pack()
root.mainloop()
I assume you want to show new number in place of old number, not below it.
import tkinter as tk
import random
def start():
# hide button
button.pack_forget()
# run `add_number` first time
add_number(level+2)
def add_number(x):
num = random.randint(1, 100)
my_list.append(num)
label['text'] = num
if x > 0:
# repeat after 2000ms (2s)
root.after(2000, add_number, x-1)
else:
# show button again after the end
button.pack()
# --- main ---
my_list = []
level = 1
root = tk.Tk()
label = tk.Label(root)
label.pack()
button = tk.Button(root, text="Click to start game", command=start)
button.pack()
root.mainloop()
Just use this simple command in your function root.update()

Categories