Tkinter lag at the "update" command - python

I made a timer just to test something out. and for some reason it starts lagging, here is the timer:
from tkinter import *
from time import sleep
main = Tk()
main.title("Timer")
c = Canvas(main,width=1000,height=400)
c.pack()
c.config(bg="black")
hours = -1
while True:
hours += 1
for minutes in range(60):
for seconds in range(60):
c.create_rectangle(0,0,1000,400,fill="black")
c.create_text(500,200,text=(str(hours)+":"+str(minutes)+":"+str(seconds)),font=("",200),fill="white")
c.update()
sleep(1)
Can someone figure out where it happens? I noticed I can run the timer without tkinter and just with print, but I need to use tkinter for other projects too.

You are creating text widgets covered by black rectangles in succession, without ever removing them - in essence, every second, you are piling two more canvas items on top of all the previous ones!
The correct approach is to use the tkinter.mainloop in conjunction with the tkinter.after method, and steer clear from using a while loop, and tkinter.update. You can change the text displayed by a canvas text item using itemconfigure.
The use of time.sleep in a GUI is a recipe to have your GUI stop responding to interactions - don't do that!
Maybe do this instead:
import tkinter as tk
def update_clock():
clock_text = f'{hours}:{str(minutes).zfill(2)}:{str(seconds).zfill(2)}'
canvas.itemconfigure(clock, text=clock_text)
main.after(1000, _increment_time)
def _increment_time():
global clock, hours, minutes, seconds
seconds += 1
if seconds == 60:
seconds = 0
minutes += 1
if minutes == 60:
minutes = 0
hours += 1
update_clock()
main = tk.Tk()
main.title('Timer')
canvas = tk.Canvas(main, width=1000, height=400, bg='black')
canvas.pack()
hours, minutes, seconds = 0, 0, 0
clock = canvas.create_text(500, 200, font=('', 200), fill='white')
update_clock()
main.mainloop()

Related

How to make a tkInter overlay UI clickthrough (non-clickable)?

I got the source code of a timer that synchronizes with the timer of a videogame bomb but the problem is, when I'm playing the game and move my mouse and click it, I accidentally click the Timer UI and it makes the command prompt where the script is running pop up and it's really gamebreaking.
I was wondering if there's any way to make the UI clicktrough so even if I click it accidently the application does not pop up.
Here's the UI's code:
import tkinter as tkr
from python_imagesearch.imagesearch import imagesearch
root = tkr.Tk()
root.geometry("+0+0")
root.overrideredirect(True)
root.wm_attributes("-topmost", True)
root.wm_attributes("-alpha", 0.01)
root.resizable(0, 0)
timer_display = tkr.Label(root, font=('Trebuchet MS', 30, 'bold'), bg='black')
timer_display.pack()
seconds = 44
//in this space i print a lot of lines creating a box with text explaining what the script does in it.
def countdown(time):
if time > 0:
mins, secs = divmod(time, 60)
ID = timer_display.bind('<ButtonRelease-1>', on_click)
timer_display.unbind('<ButtonRelease-1>', ID)
def color_change(t_time):
if t_time > 10:
return 'green'
elif 7 <= t_time <= 10:
return 'yellow'
elif t_time < 7:
return 'red'
timer_display.config(text="{:02d}:{:02d}".format(mins, secs),
fg=color_change(time)), root.after(1000, countdown, time - 1)
else:
root.wm_attributes('-alpha', 0.01)
search_image()
def start_countdown():
root.wm_attributes('-alpha', 0.7)
countdown(seconds)
def search_image():
pos = imagesearch("./github.png")
pos1 = imagesearch("./github1.png")
if pos[0] & pos1[0] != -1:
start_countdown()
else:
root.after(100, search_image)
root.after(100, search_image)
root.mainloop()
If the UI you are talking about is timer_display then I suppose you are using bind function for the clicking part you were talking about. Here is what you can try:
# Save the binding ID
ID = timer_display.bind('<ButtonRelease-1>', on_click)
Then use it to unbind <ButtonRelease-1>
timer_display.unbind('<ButtonRelease-1>', ID)
Maybe this question might be of some help.

Tkinter After Loop Only Runs to Time if Mouse Moving

I'm using after() for a Tkinter animation loop:
from tkinter import Tk, Canvas
from time import time
root = Tk()
root.configure(width=1920, height=1080)
root.resizable(True, True)
main = Canvas(root, width=1600, height=900)
ball = main.create_oval(80, 80, 120, 120, fill="#FF0000")
main.pack()
frametarget = 17
lasttime = time()
frametimes = []
def move():
global lasttime
frametimes.append(time() - lasttime)
lasttime = time()
main.move(ball, 1, 1)
root.after(frametarget, move)
root.after(0, move)
root.mainloop()
print(sum(frametimes[60:]) / len(frametimes[60:]) * 1000)
This generally seems to be updating in the region of ~30ms and looks visually slow when left unattended, but when the mouse is continuously moving over the window it hits 17ms consistently and runs smooth, but reverts back once this stops.
I'm using Python 3.7, Windows 10
Tests:
Average frame time where frametarget = 17 (for ~60fps)
Without mouse movement = 29.28ms
With mouse movement = 17.08ms
Average frame time where frametarget = 50 (for 20fps)
Without mouse movement = 62.50ms
With mouse movement = 50.04ms
Update:
The same issue is not present on Linux.
Upgrading to Python 3.8.6 (from 3.7.4) solved the issue.
It appeared to be unique to my own environment as the problem did not reproduce on OSX, Linux or other Windows environments.
I had almost the same problem except in my case I didn't have to move the mouse, just hover with it on the window, but I think this solution will work for both cases.
Add this at the start of your code:
from ctypes import windll
timeBeginPeriod = windll.winmm.timeBeginPeriod
timeBeginPeriod(1)

How to change countdown to hour/minutes/seconds

I am new to Tkinter so im just trying to learn as much as possible. I want to try and make an alarm clock but now im stuck on the time format. This is the current code:
from tkinter import *
teer = Tk()
field = Canvas(teer, bd=0, highlightthickness=0, height='190', width='400', bg='#111111')
field.pack()
def start_countdown(count):
coin = 0.5
teer.resizable(False,False)
counter = Label(teer, fg = "#287aff", bg='#232323', font = ("Lato", 35, "bold"), width='15')
counter.place(x=50, y=50)
counter["text"] = count
if count > 0:
teer.after(1000, start_countdown, count -1)
if count < 500:
coin = 0.6
if count < 300:
coin = 0.7
start_countdown(500)
teer.mainloop()
now what i've been trying to do is chop the 500 (seconds) up into minutes / seconds. Or ultimately change it to hours / minutes / seconds if i may choose to insert an int larger than 3600 into the function. I just want the time hardcoded so i thought it wouldn't be such a problem.
What i tried:
-Experimented with different alarms / countdowns that people made (sadly there aren't many out there that count down instead of up and are also in hours/minutes/seconds.
-Experimented with the format (for example) %H:%M:%S
I Just don't seem to get it.
Would appreciate any help or advice about making a GUI-program that counts down.
You can use divmod to calculate the remaining time.
import tkinter as tk
root = tk.Tk()
a = tk.Label(root,text="")
a.pack()
def set_idle_timer(t):
hours, remainder = divmod(t, 3600)
mins, secs = divmod(remainder, 60)
timeformat = "{:02d}:{:02d}:{:02d}".format(hours, mins, secs)
a.config(text=timeformat)
t -=1
root.after(1000,lambda: set_idle_timer(t))
set_idle_timer(3605)
root.mainloop()

Tkinter clock crashing with infinite loop?

I'm creating a single StringVar called time_display, which contains a string of the fashion 3:14:02, and a single label in the window which should (in theory) reflect updates to said StringVar. However, when I call updateTime to check the time and update it, I crash in an infinite loop. Other adjustments I have made have resulted in no infinite loop but no updating clock either. What am I doing wrong?
from tkinter import *
from tkinter import ttk
import time
root = Tk()
root.title("Clock")
def updateTime(var):
hours = time.localtime().tm_hour
if hours == 0:
hours = 12
minutes = time.localtime().tm_min
if minutes < 10:
minutes = '0' + str(minutes)
else:
minutes = str(minutes)
seconds = time.localtime().tm_sec
if seconds < 10:
seconds = '0' + str(seconds)
else:
seconds = str(seconds)
current_time = str(hours) + ':' + minutes + ':' + seconds
var.set(current_time)
root.after(500, updateTime(var))
time_display = StringVar()
updateTime(time_display)
ttk.Label(root, textvariable=time_display).grid(column=0, row=0)
root.mainloop()
In following line, the code is calling updateTime directly; causing recursive call.
root.after(500, updateTime(var))
# ^ ^
Pass the function and argument without calling it will solve your problem.
root.after(500, updateTime, var)
Alternatively you can use lambda:
root.after(500, lambda: updateTime(var))
BTW, using time.strftime, updateTime can be reduced:
def updateTime(var):
var.set(time.strftime('%H:%M:%S'))
root.after(500, updateTime, var)

Tkinter events outside of the mainloop?

The program I am writing has a tkinter window that is constantly being fed with data manually rather than being part of a mainloop. It also needs to track mouse location. I havn't found a workaround for tracking the mouse outside of mainloop yet, but if you have one please do tell.
from Tkinter import *
import random
import time
def getCoords(event):
xm, ym = event.x, event.y
str1 = "mouse at x=%d y=%d" % (xm, ym)
print str1
class iciclePhysics(object):
def __init__(self, fallrange, speed=5):
self.speed = speed
self.xpos = random.choice(range(0,fallrange))
self.ypos = 0
def draw(self,canvas):
try:
self.id = canvas.create_polygon(self.xpos-10, self.ypos, self.xpos+10, self.ypos, self.xpos, self.ypos+25, fill = 'lightblue')
except:
pass
def fall(self,canvas):
self.ypos+=self.speed
canvas.move(self.id, 0, self.ypos)
root = Tk()
mainFrame = Frame(root, bg= 'yellow', width=300, height=200)
mainFrame.pack()
mainCanvas = Canvas(mainFrame, bg = 'black', height = 500, width = 500, cursor = 'circle')
mainCanvas.bind("<Motion>", getCoords)
mainCanvas.pack()
root.resizable(0, 0)
difficulty = 1500
#root.mainloop()
currentIcicles = [iciclePhysics(difficulty)]
root.update()
currentIcicles[0].draw(mainCanvas)
root.update_idletasks()
time.sleep(0.1)
currentIcicles[0].fall(mainCanvas)
root.update_idletasks()
tracker = 0
sleeptime = 0.04
while True:
tracker+=1
time.sleep(sleeptime)
if tracker % 3 == 0 and difficulty > 500:
difficulty -= 1
elif difficulty <= 500:
sleeptime-=.00002
currentIcicles.append(iciclePhysics(difficulty))
currentIcicles[len(currentIcicles)-1].draw(mainCanvas)
for i in range(len(currentIcicles)):
currentIcicles[i].fall(mainCanvas)
root.update_idletasks()
for i in currentIcicles:
if i.ypos >= 90:
currentIcicles.remove(i)
root.update_idletasks()
There is no way. Mouse movement is presented to the GUI as a series of events. In order to process events, the event loop must be running.
Also, you should pretty much never do a sleep inside a GUI application. All that does is freeze the GUI during the sleep.
Another hint: you only need to create an icicle once; to make it fall you can use the move method of the canvas.
If you are having problems understanding event based programming, the solution isn't to avoid the event loop, the solution is to learn how event loops work. You pretty much can't create a GUI without it.

Categories