Python tkinter after method not working as expected - python

I'm trying to learn Tkinter module, but I can't undestand why the after method doesn't behave as expected. From what I know, it should wait ms milliseconds and then execute the function, but in my case the function gets executed many more time, not considering the time I write. Here's the code:
from tkinter import *
def doSomething():
x = int(l["text"])
l["text"] = str(x + 1)
root = Tk()
root.geometry("300x300")
l = Label(root, text="0")
l.pack()
while True:
l.after(1000, doSomething)
root.update()
if int(l["text"]) >= 5:
break
root.mainloop()
After the first 2 seconds the label starts displaying humongous numbers

After the first 2 seconds the label starts displaying humongous numbers
Keep in mind, while True, is an infinite loop, you are making infinite calls to root.after() means alot of events are being scheduled to be called after 1 second. Better way to do this is to remove your while and move it all inside your function.
from tkinter import *
root = Tk()
def doSomething():
x = int(l["text"])
l["text"] = x + 1
if int(l["text"]) < 5: # Only repeat this function as long as this condition is met
l.after(1000, doSomething)
root.geometry("300x300")
l = Label(root, text="0")
l.pack()
doSomething()
root.mainloop()
Though the best way to write the function would be to create a variable and increase the value of that variable inside the function and then show it out:
from tkinter import *
root = Tk()
count = 0 # Initial value
def doSomething():
global count # Also can avoid global by using parameters
count += 1 # Increase it by 1
l['text'] = count # Change text
if count < 5:
l.after(1000, doSomething)
root.geometry("300x300")
l = Label(root, text=count)
l.pack()
doSomething() # If you want a delay to call the function initially, then root.after(1000,doSomething)
root.mainloop()
This way you can reduce the complexity of your code too and make use of the variable effectively and avoid nasty type castings ;)

You are using infinite loop when using while True. Correct way is:
from tkinter import *
def doSomething():
x = int(l["text"])
l["text"] = str(x + 1)
if x < 5:
l.after(1000, doSomething)
root = Tk()
root.geometry("300x300")
l = Label(root, text="0")
l.pack()
doSomething()
root.mainloop()

Related

How do i stop the countdown and reset the starting value, without exiting the mainloop?

How do i stop the countdown and reset the starting value, without exiting the mainloop?
Code:
from tkinter import *
root = Tk()
i=100
def countdown():
global l1, i, root
l1.config(text=i)
i -= 1
root.after(1000, countdown)
l1=Label(root, text='' )
l1.pack()
countdown()
root.mainloop()
You need to use after_cancel(id) to stop the after() calls. So it goes something like:
def countdown():
global i
l1.config(text=i)
i -= 1
rep = root.after(1000, countdown)
if i < 0: # Use i <= 0 if you don't want to include 0
root.after_cancel(rep)
You can further improve your code by removing the global and passing the current reduced time as parameter to the function, like:
from tkinter import *
root = Tk()
i = 100
def countdown(sec):
l1.config(text=sec)
sec -= 1
rep = root.after(1000,countdown,sec) # Passes sec as argument to countdown
if sec < 0:
root.after_cancel(rep)
l1 = Label(root)
l1.pack()
countdown(i) # Pass initial time as sec parameter to the function
root.mainloop()
Also keep in mind, there is no guarantee after(ms,func) will call the function after exactly ms millisecond, it can get delayed further to maybe ms+0.001 or ms+0.5 or whatever milliseconds as well. The only guarantee is that, it will not call the function before the supplied ms.

Fast changing labels in Tkinter?

I would like to display some numbers, as fast as possible in Tkinter. The Program, I am trying to do, gets many numbers send and should show those.
Here is an similar environment, where tinter has to change a label very quickly.
from tkinter import *
import time
window = Tk()
lbl13 = Label(window, text="-")
lbl13.grid(column=0, row=0)
x = 0
while 1:
lbl13.config(text = str(x))
time.sleep(2)
x +=1
window.mainloop()
The Tkinter window doesn't even open on my computer. Is that because i have too weak hardware? What could I change that this Program also runs on my Computer. Thank you for every answer!
The infinite while loop will keep the program from getting to the line where you call window.mainloop(). You should call window.update() repeatedly instead of window.mainloop() at the end:
from tkinter import *
import time
window = Tk()
lbl13 = Label(window, text="-")
lbl13.grid(column=0, row=0)
x = 0
while 1:
lbl13.config(text = str(x))
window.update()
x +=1
Using after and a proper mainloop is probably a more more flexible way to achieve what you want; it is also reusable in different contexts, and can be used in an application that does more than trivially increment a number on a label:
maybe something like this:
import tkinter as tk
if __name__ == '__main__':
def increment():
var.set(var.get() + 1)
label.after(1, increment)
window = tk.Tk()
var = tk.IntVar(0)
label = tk.Label(window, textvariable=var)
label.pack()
increment()
window.mainloop()

How to update labels in tkinter dynamically?

I've created this code that updates a label every second to indicate that something is loading (run the code to see what I mean). I'm using the threading module with tkinter but I feel like there must be a more efficient way to do this.
Here is my code:
from tkinter import *
from time import sleep
import threading
root = Tk()
new_var = StringVar()
new_var.set('Loading')
def change_text():
array = [".", "..", "...", ""]
while True:
for num in range(4):
sleep(1)
new_var.set(f"Loading{array[num]}")
root.update_idletasks()
l = Label(root, textvariable = new_var)
l.pack()
Loading_animation = threading.Thread(target=change_text)
Loading_animation.start()
root.mainloop()
Also, if there isn't a better way to do this how do I prevent the error that I keep receiving whenever I close the root window?
Thank you!
Here is a simpler method that doesn't involve threading.
Keep a counter and every second call the function. In the function simply set the text to each item in the list by the counter as an index.
Update: To answer your question in the comments.
This will not get stuck in some loop that stops us from reaching the mainloop() because this code only adds a command to be run on the event list at a regular interval of 1 second. What is actually happening is the after() method will add a new even to run no sooner than 1 second (1000 milliseconds). Because Tkinter is event-driven Tkinter will handle each even in the list as it comes after every mainloop() cycle.
import tkinter as tk
root = tk.Tk()
counter = 0
def change_text():
global counter
my_list = [".", "..", "...", ""]
if counter != 3:
l.config(text="Loading{}".format(my_list[counter]))
counter += 1
root.after(1000, change_text)
else:
l.config(text="Loading{}".format(my_list[counter]))
counter = 0
root.after(1000, change_text)
l = tk.Label(root, text = "")
l.pack()
change_text()
root.mainloop()
Here is the same answer as #Mike-SMT, but using the cycle function to make it a lot neater.
import tkinter as tk
from itertools import cycle
root = tk.Tk()
my_list = cycle([".", "..", "...", ""])
def change_text():
l.config(text="Loading{}".format(next(my_list)))
root.after(1000, change_text)
l = tk.Label(root)
l.pack()
change_text()
root.mainloop()

Deleting functions in running scripts? Tkinter Python 3.5

In tkinter, python, I'm trying to make a program so when Up is clicked, 1 is added to a number (using StringVar to show it), and when the number gets to 10, it is restarted to 0. I've managed to complete this part, but I want to enable it so after a certain amount of time, the number can no longer be changed. For example, I hold down the Up button for a certain given amount of time, and the last number I get is a 6. Now, I want it to stay on 6, no matter how many times I press Up. Here's my code:
from tkinter import *
root = Tk()
hwd1 = 0
mult = 1
hwd = StringVar()
hwd.set("0")
def add1(event):
global hwd
global hwd1
on = True
if on:
hwd1 += 1*(mult)
hwd.set(str(hwd1));
if hwd1 == 10:
hwd1 = 0
hwd.set("0")
label = Label(root, textvariable=hwd)
label.pack()
root.bind_all('<Up>', add1)
This code shows the first part of what I have done, but when I try to do the second part, here's what I done:
from tkinter import *
root = Tk()
hwd1 = 0
mult = 1
hwd = StringVar()
hwd.set("0")
def deladd():
global add1
del(add1)
def add1(event):
global add1
global hwd
global hwd1
on = True
if on:
hwd1 += 1*(mult)
hwd.set(str(hwd1));
if hwd1 == 10:
hwd1 = 0
hwd.set("0")
root.after(5000, deladd)
label = Label(root, textvariable=hwd)
label.pack()
root.bind_all('<Up>', add1)
From using this code, I get an error message saying add1 referenced before assignment and I'm not sure why. I'd appreciate some help, so thanks for your time :)
-Jake
You're missing a declaration - just edit your code and put def add1 before def deladd
The problem is because deladd() is calling add1, but on the first call it can't 'reach' it.

When using tkinter in Python 3, how do you get a button to run a function each time it is pressed?

import tkinter as tk
panel = tk.Tk()
num = 42
lbl1 = tk.Label(panel, text = str(num))
Let's say I have a function and button like this:
def increase():
lbl1.configure(text = str(num+1))
btn = tk.Button(panel, text = 'Increase', command = increase)
panel.mainloop()
This button will make the number that is the label increase by 1 when pressing the button. However, this only works once before the button does absolutely nothing. How can I make it so that every time I press the button, the number increases by 1?
You never saved the incremented value of num.
def increase():
global num # declare it a global so we can modify it
num += 1 # modify it
lbl1.configure(text = str(num)) # use it
It's because num is always 43
import tkinter as tk
num = 42
def increase():
global num
num += 1
lbl1.configure(text = str(num))
panel = tk.Tk()
lbl1 = tk.Label(panel, text = str(num))
lbl1.pack()
btn = tk.Button(panel, text = 'Increase', command = increase)
btn.pack()
panel.mainloop()

Categories