Tkinter very odd behaviour when using the "root.after(1000)" function - python

in tkinter I am trying to create a stopwatch/timer and I have the basic code done however something odd happens and I have no idea how to explain it so just see this video.
https://imgur.com/a/H38faKM
(and if just keeps on going)
Here is the code that handles the timer:
def TimerUpdate():
global TimerVar
TimerVar = TimerVar - 1
Timer.config(text = f"{TimerVar}")
root.after(1000, TimerUpdate)
def Update():
global TimerOn
now = datetime.now()
time = now.strftime("%H:%M:%S")
Time.config(text = f"{time}")
if TimerOn == True:
root.after(1000, TimerUpdate)
else:
root.after(1000, Update)
And here is the code that mentions the "TimerOn" variable
def Start():
global TimerOn
Timer.config(font = ("Times New Roman", 50))
Timer.place(x = "160", y = "250")
TimerOn = True
I am obviously trying to make it go down each second but I have no idea whats happening here.

After TimerOn = True the update function checks if TimerOn == True and since it's True it keeps calling the TimerUpdate function every time. Try making TimerOn = False after calling TimerUpdate:
if TimerOn == True:
root.after(1000, TimerUpdate)
TimerOn = False
else:
root.after(1000, Update)

Related

Detect NO KEY PRESSES with keyboard module python

I'm making a tkinter program and want to detect a key press and do something when there is, however if no key is pressed the keyboard.read_key() will not execute and not let any other code run until a key is pressed.
Is there an alternative I can use or a way to check if no key was pressed?
def on_press():
global running
global turn
if keyboard.read_key() == 'q':
ws.destroy()
elif keyboard.read_key() == 'a':
if turn:
print('turning off')
running = False
turn = False
else:
print('turning on')
running = True
turn = True
start()
else:
start()
def start():
global running
global turn
print('start')
if running:
print('clicking')
mouse.click(Button.left, cps_val)
time.sleep(1)
on_press()
on_press()
Key is no pressed for the most of the time. Because computer is fast and human is slow so I would assumed that that key is no pressed at the beginnig and I would run code at once (without checking if it no pressed). And I would run "clicking code" all time in while at the beginning and I would use keys only to pause this loop (key a) or exit this loop (key q) - without checking if key is no pressed.
import keyboard
#import mouse
import time
# --- functions ---
def pause():
global turn
if turn:
print('turning off')
turn = False
else:
print('turning on')
turn = True
def finish():
global running
running = False
def main():
global counter
print('start')
while running:
if turn:
counter += 1
print('clicking', counter)
#mouse.click(Button.left, cps_val)
time.sleep(1)
# --- main ---
turn = True
running = True
counter = 0
keyboard.add_hotkey('q', finish)
keyboard.add_hotkey('a', pause)
main()
If it has to run with tkinter then while loop will block tkinter (tkinter will freeze because it will have no time to get key/mouse events from system, send them to widgets, update widgets, and redraw widgets in window).
I would use tkinter.after(milliseconds, function) instead of sleep() and while and tkinter will have time to work.
import tkinter as tk
import keyboard
#import mouse
import time
# --- functions ---
def pause():
global turn
if turn:
print('turning off')
turn = False
else:
print('turning on')
turn = True
def finish():
global running
running = False
root.destroy()
def main():
print('start')
loop() # run first time at once
#root.after(1000, loop) # run first time after 1s
def loop():
global counter
if turn:
counter += 1
#print('clicking', counter)
label['text'] = f'clicking {counter}'
#mouse.click(Button.left, cps_val)
if running:
root.after(1000, loop) # run again after 1s
# --- main ---
turn = True
running = True
counter = 0
keyboard.add_hotkey('q', finish)
keyboard.add_hotkey('a', pause)
root = tk.Tk()
label = tk.Label(root, text='clicking 0')
label.pack()
main()
root.mainloop()

Python Tkinter Timer

I am trying to make a clock in Tkinter and I am trying to create a timer however, whenever I try to start it the label just remains at the number chosen.
For the clock, I have an update variable that is always refreshing so I am trying to incorporate the timer into that. This is the update code:
def Update():
now = datetime.now()
time = now.strftime("%H:%M:%S")
Time.config(text = f"{time}")
if TimerOn == True:
root.after(1000, Update)
root.after(1000, TimerUpdate)
else:
root.after(1000, Update)
The "TimerOn" Variable is set true here:
def Start():
Timer.config(font = ("Times New Roman", 50))
Timer.place(x = "160", y = "250")
TimerOn = True

After() method in tkinter for timer

I want to start a timer when the user clicks a button for the first time in my number click game. I tried to use the after method for this, but when I click a button, the timer stays at 0. The rest of the code works fine without any error messages.
Here's the code:
import tkinter as tk
from random import randint
# create window
window = tk.Tk()
window.title('Clicker')
# create list for random numbers
new_list = []
# define time count:
def time_event():
global current_time, after_id
if clock_started:
current_time += 1
clock["text"] = str(current_time)
after_id = clock.after(1000, time_event)
# define click event
def click_event(event):
global clock_started
new_button = event.widget
clicked_val = int(new_button["text"])
clock_started = True
if not clock_started:
clock_started = True
clock.after(1000, time_event)
if clicked_val == new_list[0]:
del new_list[0]
new_button["state"] = tk.DISABLED
if len(new_list) == 0:
clock.started = False
clock.after_cancel(after_id)
# create buttons
for i in range(25):
new_num = randint(1, 999)
while i in new_list:
new_num = randint(1, 999)
new_list.append(new_num)
new_list.sort()
new_button = tk.Button(window, text=new_num)
new_button.grid(column=i // 5, row=i % 5)
new_button.bind("<Button-1>", click_event)
# create clock
current_time = 0
clock = tk.Label(window, text=str(current_time))
clock.grid(column=2, row=6)
clock_started = False
# run game
window.mainloop()
In your code, clock_started has been initialized to True which implies that this condition if not clock_started: will not be satisfied to begin with and hence the timer doesn't work without giving an error. Your final click_event(event) should look like this:
def click_event(event):
global clock_started
new_button = event.widget
clicked_val = int(new_button["text"])
clock_started = False
if not clock_started:
clock_started = True
clock.after(1000, time_event)
if clicked_val == new_list[0]:
del new_list[0]
new_button["state"] = tk.DISABLED
if len(new_list) == 0:
clock.started = False
clock.after_cancel(after_id)

how to stop timer at 20 minutes

I am making a timer that starts when the user hits "space" and stops on "p", showing the ending time. I can I stop it at a maximum time of 20 minutes? Is there something like
if time_passed==20:
break
My code:
from turtle import*
from datetime import datetime
...
def start():
undo()
global break1, timerint, startime
break1 = 0
startime = datetime.now()
while True:
timerint = datetime.now()-startime
write(timerint,font=("Arial",50))
undo()
if break1 == 1:
break
def stop():
global break1, timerint, startime
timerint=datetime.now()-startime
write(timerint,font=("Arial",50))
break1 = 1
# Turtle placement code removed
onkeypress(start,"space")
onkeypress(stop,"p")
listen()
No, but you can always check elapsed time with the time.time() method.
import time
start = time.time()
while ...
....
now = time.time()
if now - start > 20 * 60:
break
That's the low-tech version. If you want more sophisticated things, such as a separate timer process, try a full browser search for "Python timer process".
Also, you might consider using Boolean values:
global timer_running, timerint, startime
timer_running = True
startime = datetime.now()
while timer_running:
timerint = datetime.now()-startime
write(timerint,font=("Arial",50))
undo()
def stop():
global timer_running, timerint, startime
timerint = datetime.now()-startime
write(timerint, font=("Arial", 50))
timer_running = False
I recommend getting rid of the while loop and instead build upon turtle's ontimer() events:
from turtle import Turtle, Screen
from datetime import datetime
FONT = ("Arial", 50)
def start():
global timer_running, start_time
if timer_running:
return
start_time = datetime.now()
timer_running = True
screen.ontimer(lambda time=start_time: automatic_stop(time), 20 * 60 * 1000)
screen.ontimer(update, 100)
def update():
if not timer_running:
return
timerint = datetime.now() - start_time
marker.undo()
marker.write(timerint, align='center', font=FONT)
screen.ontimer(update, 100)
def manual_stop():
global timer_running
if not timer_running:
return
timer_running = False
timerint = datetime.now() - start_time
marker.undo()
marker.write(timerint, align='center', font=FONT)
def automatic_stop(time):
global timer_running
if timer_running and start_time == time: # make sure *this* timer is still valid
timer_running = False
marker.undo()
marker.write("Expired!", align='center', font=FONT)
screen = Screen()
marker = Turtle(visible=False)
marker.penup()
marker.write("Hit 'space' to start timer; 'p' to stop", align='center', font=FONT)
start_time = None
timer_running = False
screen.onkeypress(start, "space")
screen.onkeypress(manual_stop, "p")
screen.listen()
screen.mainloop()
We pass automatic_stop() a copy of start_time so that when it wakes up in the distant future it can check if it is still a valid end event or not based on the current start_time. (If you work at the Tk level instead of turtle, you might be able to cancel the timer when no longer needed.)

"after" looping indefinitely: never entering mainloop

This is my first post. I started coding when considering a career swap two months ago and am working on a Tetris clone. I've implemented most of the core features, but cannot get the game to refresh continually with an after loop.
I'm using Tkinter to produce my Gui and am trying out event oriented programming.
My understanding is that after(Time, Event) from Tkinter should schedule whatever the Event callback function is to occur after a delay specified by Time. I think that the code is supposed to continue executing subsequent items after this.
My frame refresh function (game.updateBoard()) does most of the necessary events for tetris to work, then calls itself using after. I call it once when initializing an instance of the game.
Instead of proceeding to mainloop(), the game.updateboard() function calls itself via after indefinitely.
I suspect that it is not behaving how I thought after worked which would be to continue to execute the script until the specified delay occurs. I think it is waiting for the callback to terminate to continue.
I tried to find a resource on this but could not.
If you have suggestions for fixing this question, the attached code, or for coding in general, I am very happy to hear them! This is a learning process and I'll gladly try pretty much anything you suggest.
Here is the relevant portion of the code:
class game():
def __init__(self): #Set up board and image board
self.pieces = ["L","J","S","Z","T","O","I"]
self.board = boardFrame()
self.root = Tk()
self.root.title("Tetris")
self.root.geometry("250x525")
self.frame = Frame(self.root)
#set up black and green squares for display
self.bSquare = "bsquare.gif"
self.gSquare = "square.gif"
self.rSquare = "rsquare.gif"
self.image0 = PhotoImage(file = self.bSquare)
self.image1 = PhotoImage(file = self.gSquare)
self.image2 = PhotoImage(file = self.rSquare)
#get an initial piece to work with
self.activeBlock = piece(self.pieces[random.randint(0,6)])
#Tells program to lower block every half second
self.blockTimer = 0
self.updateBoard()
self.root.bind('<KeyPress-Up>', self.turn)
self.root.bind('<KeyPress-Right>', self.moveR)
self.root.bind('<KeyPress-Left>', self.moveL)
self.root.bind('<KeyPress-Down>',self.moveD)
print("Entering mainloop")
self.root.mainloop()
def turn(self, event):
self.activeBlock.deOccupy(self.board)
self.activeBlock.turn()
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveR(self, event):
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([1,0], self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveL(self, event):
if self.activeBlock.checkLeft(self.board) == False:
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([-1,0], self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveD(self, event): #find
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([0,-1],self.board)
if self.activeBlock.checkBottom(self.board) == True:
self.activeBlock.occupy(self.board)
self.activeBlock = piece(self.pieces[random.randint(0,6)])
## self.activeBlock = piece(self.pieces[1])
print("bottomed")
self.activeBlock.occupy(self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def drawGrid(self, dGrid):
#Generate squares to match tetris board
for widget in self.frame.children.values():
widget.destroy()
self.activeBlock.occupy(self.board)
for x in range(9,-1,-1):
for y in range(20,-1,-1):
if self.board.grid[x][y] == 1:
self.frame.displayA = Label(self.frame, image=self.image1)
## self.frame.displayA.image = self.image1
self.frame.displayA.grid(row=21-y, column=x)
else:
self.frame.displayA = Label(self.frame, image = self.image0)
## self.frame.displayA.image = self.image0
self.frame.displayA.grid(row=21-y, column=x)
self.frame.displayA = Label(self.frame, image = self.image2)
self.frame.displayA.grid(row = 21 - self.activeBlock.center[1], column = self.activeBlock.center[0])
self.frame.grid()
def updateBoard(self):
self.blockTimer += 1
"print updateBoard Loop"
## 1)check for keyboard commands
#1.1 move block by keyboard commands
#2) see if block has bottomed out, if it has, have it enter itself into the grid and generate a new block.
if self.activeBlock.checkBottom(self.board) == True:
self.activeBlock.occupy(self.board)
self.activeBlock = piece(self.pieces[random.randint(0,6)])
print("bottomed")
self.activeBlock.occupy(self.board)
#2.2 - if block has not bottomed and 50 frames (~.5 seconds) have passed, move the active block down a square after clearing its old space.
elif self.blockTimer%12 == 0:
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([0,-1], self.board)
self.activeBlock.occupy(self.board)
## 4) check for filled rows
for y in range(1,21):
for x in range(10):
rowFull = True
if self.board.grid[x][y] == 0:
rowFull == False
#4.1 if any row is filled, delete it and then move all rows above the deleted row down by one
if rowFull == True:
for x2 in range(10):
self.board.grid[x2][y] = 0
for y2 in range(y+1,21):
if self.board.grid[x2][y2] == 1:
self.board.grid[x2][y2] = 0
self.board.grid[x2][y2-1] = 1
#4.11 if the row is full and the row above it was full, delete the row again as well as the row above it, and move all rows down by 2
for x in range(10):
rowFull = True
if self.board.grid[x][y] == 0:
rowFull == False
if rowFull == True:
for x2 in range(10):
try:
self.board.grid[x2][y] = 0
self.board.grid[x2][y+1] = 0
except:
pass
for y2 in range(y+2,21):
try:
if self.board.grid[x2][y2] == 1:
self.board.grid[x2][y2] = 0
self.board.grid[x2][y2-2] = 1
except:
pass
#5) if there is a block in the top row, end the game loop
for x in range(10):
if self.board.grid[x][20] == 1:
game = "over"
#6) update image
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
self.frame.after(500, self.updateBoard())
Game = game()
You want to do self.frame.after(500, self.updateBoard).
The difference here is subtle, (self.updateBoard instead of self.updateBoard()). In your version, you're passing the result of your function to the after method instead of passing the function. This results in the infinite recursion that you described.

Categories