After() method in tkinter for timer - python

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)

Related

How can I use a single button to pause and unpause timer in tkinter? (without using pygame)

I'm working on a simple timer that counts down 30 mins for me to study and 5 mins for a break. So far, the start_timer function and count_down function work well, I just cannot figure out how to write the pause function. I did some research for a few days. Most articles are using pygame or to bind with different keys. I am wondering what function I should use for one tkinter button to pause/unpause my timer if something comes up and I want to pause the timer till I'm back.
Thank you #TimRoberts, I can pause the timer now. However, I don't know how to unpause the timer to let it continue counting down.
from tkinter import *
import math
WORK_MIN = 30
BREAK_MIN = 5
reps = 0
paused = False
# --------------------------- TIMER ---------------------------- #
def start_timer():
global reps
reps += 1
work_sec = WORK_MIN * 60
break_sec = BREAK_MIN * 60
if reps % 2 == 1:
title_label.config(text="Study")
count_down(work_sec)
else:
title_label.config(text="Break")
count_down(break_sec)
window.attributes('-topmost', 0)
# ------------------------ COUNTDOWN--------------------------- #
def count_down(count):
global paused
count_min = math.floor(count / 60)
count_sec = count % 60
if count_min < 10:
count_min = f"0{count_min}"
if count_sec < 10:
count_sec = f"0{count_sec}"
canvas.itemconfig(timer_text, text=f"{count_min}:{count_sec}" )
if count > 0:
if not paused:
count -= 1
window.after(1000, count_down, count-1)
else:
start_timer()
# ---------------------------- PAUSE ------------------------------- #
def pause_function():
global paused
paused = not paused
# ---------------------------- UI ------------------------------- #
window = Tk()
title_label = Label(text="Timer")
title_label.grid(column=1, row=0)
check_marks = Label(text="")
check_marks.grid(column=1, row=4)
canvas = Canvas(width=200, height=224, bg="lightblue")
timer_text = canvas.create_text(100, 128, text="00:00", fill="white", font=("Courier", 45, "bold"))
canvas.grid(column=1, row=1)
start_button = Button(text="Start", command=start_timer)
start_button.grid(column=0, row=2)
pause_button = Button(text="Pause", command=pause_function)
pause_button.grid(column=2, row=2)
window.mainloop()
You need to do the "after" call even if you're paused, otherwise you'll never notice when you unpause. Also, since you're decrementing count once, you don't need to do it again:
def count_down(count):
count_min = count // 60
count_sec = count % 60
canvas.itemconfig(timer_text, text=f"{count_min:02d}:{count_sec:02d}" )
if count:
if not paused:
count -= 1
window.after(1000, count_down, count)
else:
start_timer()
If you want to be tricky, you could use:
if count:
count -= not paused
since True is 1 and False is 0.

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

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)

TypeError: list indices must be integers or slices, not Button Tkinter

I am trying to make a memory game. This is what I have so far. How do I solve this problem? If there is any tips that would help in making a memory game using Tkinter, that would be much appreciated!
My code:
from tkinter import *
from tkinter import messagebox
import time
import random
difficulty = 16
rowsize= 4
columnsize = 4
numcount = 0
lastnum = 0
gotitcorrect = False
root = Tk()
root.title("MEMORY GAME!!")
root.configure(bg='gray')
def GameStart():
menuFrame.pack_forget()
gameFrame.pack()
def Timer(tim):
time.sleep(tim)
def GetRandomNumber():
lst1 = [i for i in range(1,9)]
lst2 = [i for i in range(1,9)]
random.shuffle(lst1),random.shuffle(lst2)
numlst = lst1+lst2
return numlst
def WrongOrRight(card, number):
if numcount == 0:
lastnum = number
numcount+=1
card.configure(text=str(number))
elif numcount == 1:
if number == lastnum:
gotitcorrect = True
card.configure(text=str(number))
else:
gotitcorrect = False
card.configure(text='')
numcount -= 1
menuFrame = Frame(root, bg='gray')
menu = [Label(menuFrame,text='MEMORY GAME', bg = 'gray'), Button(menuFrame,command = GameStart,text = 'Start', bg='gray')]
for i in menu:
i.pack()
menuFrame.pack()
numlst = GetRandomNumber()
print(numlst)
gameFrame = Frame(root, bg='gray')
cards = [[Button(gameFrame) for j in range(4)] for i in range(4)]
index = 1
card_dict = {}
for x in range(rowsize):
for y in range(columnsize):
print(index)
cards[x][y].grid(row = y, column = x, padx=20,pady=20)
cards[x][y].configure(text = str(numlst[index-1]))
cards[x][y].configure(command = lambda: WrongOrRight(cards[x][y],numlst[cards[x][y]]))
card_dict[cards[x][y]] = numlst[index-1]
index+=1
Timer(5)
for x in range(rowsize):
for y in range(columnsize):
cards[x][y].configure(text = '')
root.grid_rowconfigure(0,weight=1)
root.grid_columnconfigure(0,weight=1)
root.grid_rowconfigure(rowsize,weight=1)
root.grid_columnconfigure(columnsize,weight=1)
root.mainloop()
This is my error:
Exception in Tkinter callback
Traceback (most recent call last): File
"/usr/lib/python3.6/tkinter/__init__.py", line 1705, in __call__
return self.func(*args) File "memorygame.py", line 62, in <lambda>
cards[x][y].configure(command = lambda: WrongOrRight(cards[x][y],numlst[cards[x][y]])) TypeError: list indices
must be integers or slices, not Button
Here's a version of your code with a number of changes. It fixes the TypeError and gets rid of the card_dict. Its not really needed anyway because that number assigned to each card can easily be stored by adding an attribute to the Button widget representing the card. Doing this also means you only have to pass the card to the WrongOrRight() function.
All important changes have been indicated with # ALL CAPS COMMENTS. I also made cosmetic changes to the code so it followed the PEP 8 - Style Guide for Python Code in an effort to make it more readable and I strongly suggest that you read and follow the guidelines in the future yourself.
from tkinter import *
from tkinter import messagebox
import time
import random
difficulty = 16
rowsize= 4
columnsize = 4
numcount = 0
lastnum = 0
gotitcorrect = False
root = Tk()
root.title("MEMORY GAME!!")
root.configure(bg='gray')
def GameStart():
menuFrame.pack_forget()
gameFrame.pack()
def Timer(tim):
time.sleep(tim)
def GetRandomNumber():
lst1 = [i for i in range(1,9)]
lst2 = [i for i in range(1,9)]
random.shuffle(lst1), random.shuffle(lst2)
numlst = lst1+lst2
return numlst
def WrongOrRight(card): # REMOVED NO LONGER NEEDED SECOND ARGUMENT.
global lastnum, numcount, gotitcorrect # ADDED
number = card.number # ADDED
if numcount == 0:
lastnum = number
numcount += 1
card.configure(text=str(number))
elif numcount == 1:
if number == lastnum:
gotitcorrect = True
card.configure(text=str(number))
else:
gotitcorrect = False
card.configure(text='')
numcount -= 1
menuFrame = Frame(root, bg='gray')
menu = [Label(menuFrame, text='MEMORY GAME', bg='gray'),
Button(menuFrame, command=GameStart, text='Start', bg='gray')]
for i in menu:
i.pack()
menuFrame.pack()
numlst = GetRandomNumber()
print(numlst)
gameFrame = Frame(root, bg='gray')
cards = [[Button(gameFrame) for j in range(4)] for i in range(4)]
index = 1
#card_dict = {} # NOT NEEDED
for x in range(rowsize):
for y in range(columnsize):
print(index)
cards[x][y].grid(row=y, column=x, padx=20, pady=20)
cards[x][y].configure(text=str(numlst[index-1]))
# ADDED DEFAULT ARGUMENTS TO LAMBDA FUNCTION TO MAKE IT WORK PROPERLY
cards[x][y].configure(command=lambda x=x, y=y: WrongOrRight(cards[x][y]))
cards[x][y].number = numlst[index-1] # ADD ATTRIBUTE TO BUTTON WIDGET
# card_dict[cards[x][y]] = numlst[index-1] # NOT NEEDED
index += 1
#Timer(5) # DISABLED FOR TESTING
for x in range(rowsize):
for y in range(columnsize):
cards[x][y].configure(text='')
root.grid_rowconfigure(0,weight=1)
root.grid_columnconfigure(0,weight=1)
root.grid_rowconfigure(rowsize,weight=1)
root.grid_columnconfigure(columnsize,weight=1)
root.mainloop()
Cards is a list of buttons:
cards = [[Button(gameFrame) for j in range(4)] for i in range(4)]
Later in the code we have:
cards[x][y].configure(command = lambda: WrongOrRight(cards[x][y],numlst[cards[x][y]]))
the code above tries to access numlst while it uses a Button as index.

using pynput for key events instead of tkinter

Here is my entire program. I want to change from using tkinter with a gui, to using pynput no gui for get_key events part of my code. Can anyone show me what code to use? This code is a talking vending machine that reads from the machine contents list, which is a file that gets updated by the vending machine company.
I dont want to use a gui as there will be no monitor attached. It's a Raspberry.
from gtts import gTTS
import pygame
from io import BytesIO
import sys
import time
import csv
pygame.init()
if sys.version_info[0] == 3:
# for Python3
from tkinter import *
else:
# for Python2
from Tkinter import *
def say(text):
tts = gTTS(text=text, slow=False, lang='en-us', lang_check=False)
fp = BytesIO()
tts.write_to_fp(fp)
fp.seek(0)
pygame.mixer.init()
pygame.mixer.music.load(fp)
pygame.mixer.music.play()
def load_list():
with open(r"/home/pi/VendyLogProject/vendylist.csv", mode="r") as infile:
return sorted(list(csv.reader(infile)))
def refresh_list():
global vl, vl2, baseposition
new_items = load_list()
if vl != new_items:
vl = new_items
vl2 = [item[0] for item in vl]
baseposition = vl[0]
vl = load_list()
vl2 = [item[0] for item in vl]
baseposition = vl[-1] # so when reading through it reads entry 0 first, then 1
def current(event=None):
global baseposition # baseposition was defined outside of the function, therefore we call global
say(baseposition[1]+baseposition[0])
def back(event=None):
global baseposition
currentposition = vl.index(baseposition)
if currentposition == 0:
baseposition = vl[-1]
say(baseposition[1]+baseposition[0])
else:
previousposition = int(currentposition) - 1 # previousposition is previous position
baseposition = vl[previousposition]
say(baseposition[1]+baseposition[0])
def forward(event=None):
global baseposition
currentposition = vl.index(baseposition)
if currentposition == (len(vl) - 1):
baseposition = vl[0]
say(baseposition[1]+baseposition[0])
else:
nextposition = int(currentposition) + 1 # nextposition is next position
baseposition = vl[nextposition]
say(baseposition[1]+baseposition[0])
def readnumber(int):
global vl
for item in vl:
global baseposition
currentposition = vl.index(baseposition)
if int == item[0]:
baseposition = vl[vl.index(item)]
say(baseposition[1]+baseposition[0])
def help():
say("Welcome to Vendy log! Use the plus and minus keys to go through the list of snacks or push a number to hear its contents!")
root = Tk()
prompt = ' VendyLog '
label1 = Label(root, text=prompt, width=len(prompt))
label1.pack()
#keys buffer
keybuf = []
def test_after():
if keybuf:
num = ''.join(keybuf)
keybuf.clear()
def get_key(event):
keybuf.append(event.char)
event.char = ''.join(keybuf)
root.after(500,test_after)
if event.char == '-':
back()
elif event.char == '+':
forward()
elif event.char == '.':
current()
elif event.char in vl2:
readnumber(event.char)
elif event.char == '00':
help()
elif event.char == '462.':
sys.exit()
def refresh_list_and_enqueue_next_refresh():
refresh_list()
root.after(60000, refresh_list_and_enqueue_next_refresh)
refresh_list_and_enqueue_next_refresh()
root.bind_all('<Key>', get_key)
root.mainloop()
I edited this comment because after I played with OpenGL and Pygame I found the answer of how to use pynput with tkinter.
This is the code sample what I write it to test if works.
# // Imports
import tkinter, pynput
from tkinter import messagebox
# // Global variables
# // If we define variable type I found that will speed up execution a little
root:object = tkinter.Tk()
app_title:str = "Tkinter and Pynput"
app_size:tuple = (300, 150)
listener_stop:bool = False
# // Logics Keyboard
class Keyboard:
# On button pressed
# On my case I found the "Fn" button from laptop is not triggered...
#staticmethod
def Pressed(key) -> bool:
# If listener_stop is True then stop listening
if listener_stop: print("Keyboard Events are stoped!"); return False
# Else show pressed key
else: print(f"Keyboard pressed: {key}")
# On button released
#staticmethod
def Released(key) -> None:
print(f"Keyboard released: {key}")
# Listen keybboard buttons
#staticmethod
def Listener() -> None:
k_listen = pynput.keyboard.Listener(on_press=Keyboard.Pressed,
on_release=Keyboard.Released
)
k_listen.start()
# // Logics Mouse
class Mouse:
# On move
#staticmethod
def Move(x, y) -> bool:
# If listener_stop is True then stop listening
if listener_stop: print("Mouse Events are stoped!"); return False
else: print(f"Mouse: Moved to {x}x{y}")
# On scroll
#staticmethod
def Scroll(x, y, dx, dy) -> None:
where = "down" if dy < 0 else "up"
print(f"Mouse: Scrolled {where} at {x}x{y}")
# On click
# On my case I found mouse wheel press is not triggered...
#staticmethod
def Click(x, y, button, pressed) -> None:
action = "pressed" if pressed else "released"
print(f"Mouse: {button} was {action} at {x}x{y}")
# Listen keybboard buttons
#staticmethod
def Listener() -> None:
m_listen = pynput.mouse.Listener(on_move=Mouse.Move,
on_click=Mouse.Click,
on_scroll=Mouse.Scroll
)
m_listen.start()
# // Logics Define GUI
class MainApp:
def __init__(self, master):
self.master = master
# Create tkinter interface
self.X = (self.master.winfo_screenwidth() - app_size[0]) // 2
self.Y = (self.master.winfo_screenheight() - app_size[1]) // 2
self.master.wm_title(app_title)
self.master.wm_geometry(f"{app_size[0]}x{app_size[1]}+{self.X}+{self.Y}")
# Magic hapen here :P
self.Screen(self.master)
self.InputEvents()
# Define Screen Informations
def Screen(self, root) -> None:
# Set the main frame
self.frm_main = tkinter.Frame(root)
self.frm_main.pack(expand=True, fill="x", side="top")
# Defain frame components
self.title = tkinter.Label(self.frm_main, text=app_title, font=("Comic Sans MS", 18, "bold"), fg="tomato")
self.title.pack(expand=False, fill="x", side="top")
# Input events
def InputEvents(self) -> None:
Keyboard.Listener()
Mouse.Listener()
# Safe Quit
def SafeQuit(self, master:object = root) -> None:
global listener_stop
if messagebox.askokcancel(f"{app_title} Quit", f"Are you shore you want to quit {app_title}?"):
# We need to make shore if the window was closed and
# listening event are still runing, then make them stop
# You will see in fact they are not stoped (from console point view)
# for that reason we check listener_stop on Mouse Move and on Keyboard Key press.
# If for some reason the app is quit and listener has not stoped then on first action did will stop
# Mouse move after app quit >> Mouse listener will stop
# Keyboard button pressed after app quit >> Keyboard listener will stops
if listener_stop == False:
listener_stop = True
print("Events Listening are stoped!")
master.destroy()
# // Run if this is tha main file
if __name__ == "__main__":
app:object = MainApp(root)
root.protocol("WM_DELETE_WINDOW", app.SafeQuit)
root.mainloop()
I updated the code again with stop lisening keyboard and mouse event.
PS: An updated version of them can be found on my github

How to allow Tkinter GUI to detect key presses when not selected

I am trying to make a full GUI, for A game borderlands 2, however it only detects my key presses when the tkinter box is selected (in focus). In a game this is not happening, so I need a way for tkinter to detect my key presses while not selected.
This is the code so far. It is not finished, ignore the "Auto Exec" and "Auto Exit" the problem is "Auto Reload". Clicking once will turn it ON and again will turn it OFF. Just choose any number (1-4) for the weapon slot, does not affect the error.
from easygui import *
from pyautogui import *
from time import *
import os
from tkinter import *
count = 1
slot = "0"
allowReload, allowExit = False, False
def poop():
sleep(3)
print("poop")
def countdown(count):
autoexecB.config(text = count)
if count == 2:
autoexecB.config(bg = "orange")
if count == 1:
autoexecB.config(bg = "yellow")
if count == 0:
autoexecB.config(text = "Running...", bg = "#44ff00")
if count > -1:
root.after(1000, countdown, count-1)
else:
for i in range(1, 3):
sleep(1)
press("'")
sleep(0.5)
type("exec Patch.txt")
press("enter")
press("esc")
press("enter")
sleep(4)
press("esc")
pressMult("down", 4)
press("enter")
press("up")
press("enter")
sleep(1)
press("'")
sleep(0.5)
type("exec STV.txt")
press("enter")
press("esc")
autoexecB.config(text = "Auto Exec", bg = "red")
def type(text):
typewrite(text)
def pressMult(key, amount):
for i in range(1, amount+1):
press(key)
def autoexec():
countdown(3)
def info():
msgbox("Auto Exec: Runs Mods automaticly\nAuto Exit: Exits the game automaticly to the main menu using INSERT\nAuto Reload: Automaticly reloads your gun using LEFT SHIFT")
def exit():
global count
count = 1
press("esc")
pressMult("down", 4)
press("enter")
press("up")
press("enter")
sleep(2.1)
press("enter")
if choose == "FARM at Hero's Pass":
sleep(3)
keyDown("w")
sleep(0.7)
keyUp("w")
keyDown("d")
sleep(1)
keyUp("d")
keyDown("ctrl")
sleep(0.5)
press("e")
keyUp("ctrl")
count += 1
def reloadslot():
global allowReload, slot
while True:
if allowReload == True:
break
slot = str(integerbox("Enter in the weapon's slot to be reloaded"))
if slot not in ("1","2","3","4"):
msgbox("A weapon can only be in slot 1, 2, 3 or 4")
else:
break
if allowReload == True:
allowReload = False
else:
allowReload = True
def on_press(event):
print(event.keysym)
if event.keysym == "Insert" and allowExit == True:
print("exit")
exit()
if event.keysym == "Shift_L" and allowReload == True:
print("running reload")
press(";")
press("e")
press(slot)
print("done")
root = Tk()
root.bind('<KeyPress>', on_press)
root.geometry("378x134")
root.config(bg = "blue")
autoexecB = Button(text = "Auto Exec", bg = "red", font = ("calibri","13"), height = 3, width = 13, command = lambda: autoexec())
autoexitB = Button(text = "Auto Exit", bg = "red", font = ("calibri","13"), height = 3, width = 13)
autoreloadB = Button(text = "Auto Reload", bg = "red", font = ("calibri","13"), height = 3, width = 13, command = lambda: reloadslot())
infoB = Button(text = "INFO", bg = "blue", width = 26, height = 3, command = lambda: info())
exitB = Button(text = "EXIT", bg = "blue", width = 25, height = 3, command = lambda: root.destroy())
autoexecB.place(x = 0, y = 0)
autoexitB.place(x = 126, y = 0)
autoreloadB.place(x = 252, y = 0)
infoB.place(x = 0, y = 78)
exitB.place(x = 193, y = 78)
root.mainloop()
root.mainloop()
I need a way for tkinter to detect my key presses while not selected
You can't use tkinter for that. It can only detect keyboard events when it has the keyboard focus. You will have to use some platform-specific library to do what you want.

Categories