i want to animate a label text in tkinter (python). for that purpose, i am using time.sleep() method for updating 1 character in Label widget after a second but it is not updating Label widget instantly rather it is updating label at once at the end of timer. How could i fix this?. Here is my code:-
from tkinter import *
import time
global a
def update(a):
txt = 'Sample Text'
mylabel.configure(text=txt[0:a])
def timer():
global a
a = 0
lenth = len('Sample Text')
start = time.time()
while True:
# Do other stuff, it won't be blocked
time.sleep(0.1)
# When 1 sec or more has elapsed...
if time.time() - start > 1:
start = time.time()
a = a + 1
# This will be updated once per second
print("{} counter".format(a))
update(a)
# Count up to the lenth of text, ending loop
if a > lenth:
break
root = Tk()
root.geometry('300x300')
mylabel = Label(root, text="S", font=('Bell', 36, 'bold'))
mylabel.pack(pady=5)
root.after(3000, timer)
root.mainloop()
It is not recommended to use loop and time.sleep() in the main thread of a tkinter application because it will block the tkinter mainloop() from updating widgets until the loop exits. That is why you can only see the result after the loop completes.
Use .after() instead:
import tkinter as tk
root = tk.Tk()
#root.geometry('300x300')
txt = 'Sample Text'
lbl = tk.Label(root, font='Bell 36 bold', width=len(txt))
lbl.pack(pady=5)
def animate_label(text, n=0):
if n < len(text)-1:
# not complete yet, schedule next run one second later
lbl.after(1000, animate_label, text, n+1)
# update the text of the label
lbl['text'] = text[:n+1]
# start the "after loop" one second later
root.after(1000, animate_label, txt)
root.mainloop()
Related
I want to write a program that has only a button, and after pressing that, program will start making 3 labels and then change the color of each one every 1 second only once.
It looks very simple and I wrote the following code :
import tkinter as tk
from time import sleep
def function():
mylist=list()
for i in range(3):
new_label=tk.Label(window,text='* * *',bg='yellow')
new_label.pack()
mylist.append(new_label)
print('First state finished')
sleep(1)
for label in mylist:
label.config(bg='red')
print('one label changed')
sleep(1)
window = tk.Tk()
window.geometry('300x300')
btn=tk.Button(window,text='start',command=function)
btn.pack()
tk.mainloop()
First the app is look like this (that is OK):
Second its look like this (its not OK because its print on the terminal but didn't update the lable) :
Third its look like this (at the end the app must be look like this and its OK) :
But I need to see the changes in the moment and use sleep for that reason.
Thank you All.
I would recommend to use .after(delay, callback) method of the tkinter to set the colour.
Hope this is what you want.
import tkinter as tk
def start():
global mylist
mylist = list()
for i in range(3):
new_label = tk.Label(window, text='* * *', bg='yellow')
new_label.pack()
mylist.append(new_label)
delay = 1000 # delay in seconds
for label in mylist:
# Additional delay so that next color change
# is scheduled after previous label color change
delay += 1000
schedule_color_change(delay, label)
def schedule_color_change(delay, label):
print("schedule color change for:", label)
label.after(delay, set_color, label)
def set_color(label):
print("setting color of:", label)
label.config(bg="red")
window = tk.Tk()
window.geometry('300x300')
btn = tk.Button(window, text='start', command=start)
btn.pack()
tk.mainloop()
Problem
The problem is your sleep(1), because it's a function that suspends the execution of the current thread for a set number of seconds, so it's like there is a stop to the whole script
Solution
The solution is to instantiate Thread with a target function, call start(), and let it start working. So you have to use timer which is included in the threading, then a timer from the threading module (import threading)
Inside the first "for" loop, remove your sleep(1) and write for example Time_Start_Here = threading.Timer (2, function_2) and then of course Time_Start_Here.start() to start.
start_time=threading.Timer(1,function_2)
start_time.start()
Instead you have to remove the second "for" loop and write what's inside ... inside the new function that will be called. Next you need to create the function
def function_2():
for label in mylist:
label.config(bg='red')
label.pack()
print('one label changed')
As Meritor guided me, I followed the after method and wrote the following recursive code without sleep :
import tkinter as tk
def recursive(i, listt):
lbl = listt[i]
if i >= 0:
lbl.config(bg='orange')
i -= 1
lbl.after(500, recursive, i, listt)
def function():
mylist = list()
for i in range(3):
new_label = tk.Label(window, text='* * *', bg='yellow')
new_label.pack()
mylist.append(new_label)
print('all label created')
# 2 is length of list minus 1
recursive(2, mylist)
window = tk.Tk()
window.geometry('300x300')
tk.Button(window, text='start', command=function).pack()
tk.mainloop()
Most likely my code is not optimized because it uses recursive and if you know anything better please tell me
I would like to implement a very simple GUI for my project. I was previously using just Print statements to output some text and data. However, that is not very conveneint and since a person will need to operate a device that I am coding, he needs to be clearly see the instructions that I am going to display on GUI.
my code:
main()
myConnection = mysql.connector.connect( host=hostname, user=username, passwd=password, db=database )
counter = 0
window = tk.Tk()
window.title("GUI")
window.geometry("400x200")
while(1):
# OPERACIJOS KODAI:
# 0 - PILDYMAS
# 1 - KOMPLEKTAVIMAS
# 2 - NETINKAMAS KODAS
tk.Label(window,text = "Scan barcode here:").pack()
entry = tk.Entry(window)
entry.pack()
var = tk.IntVar()
button = tk.Button(window,text="Continue",command = lambda: var.set(1))
button.pack()
print("waiting...")
button.wait_variable(var)
result = entry.get()
print("Entry string=",result)
var.set(0)
operacijos_kodas=Scanning_operation(myConnection,result)
print("operacijos kodas=",operacijos_kodas)
if(operacijos_kodas == 0):
tk.label(window,text = "PILDYMO OPERACIJA:").pack()
pildymo_operacija(myConnection)
elif(operacijos_kodas == 1):
tk.Label(window,text = "PAKAVIMO OPERACIJA:").pack()
insertData_komplektacija(myConnection,"fmb110bbv801.csv");
update_current_operation(myConnection);
picking_operation();
elif(operacijos_kodas == 2):
print("Skenuokite dar karta")
#break
window.mainloop();
Nothing is being displayed. It just opens up an empty GUI window.
First of all, I am unsure where should I call function window.mainloop().
Secondly, since my system runs in an infinite while loop ( the operation starts when a user scans a bar-code, then he completes an operation and the while loop starts over again (waiting for user to scan a bar-code). So I just simply have to display some text and allow user to input data in the text box.
Could someone suggest me whether this GUI is suitable for my needs or I should look for an alternatives?
UPDATE*********************
I have tried to use mainloop:
print ("Using mysql.connector…")
main()
GPIO_SETUP()
myConnection = mysql.connector.connect( host=hostname, user=username, passwd=password, db=database )
counter = 0
window = tk.Tk()
window.resizable(False,False)
window_height = 1000
window_width = 1200
#window.attributes('-fullscreen',True)
#window.config(height=500,width=500)
#can = Canvas(window,bg='red',height=100,width=100)
#can.place(relx=0.5,rely=0.5,anchor='center')
window.title("GUI")
screen_width = window.winfo_screenwidth()
screen_height= window.winfo_screenheight()
x = int((screen_width/ 2) - (window_width / 2))
y = int((screen_height/ 2) - (window_height / 2))
window.geometry("{}x{}+{}+{}".format(window_width,window_height,x,y))
label1=Label(window,text = "SKENUOKITE BARKODA(GUID) ARBA DAIKTO RIVILINI KODA:")
label1.pack()
entry = Entry(window)
entry.pack()
var = tk.IntVar()
button = Button(window,text="Testi operacija",width = 30,command = lambda: var.set(1))
button.pack()
#button2 = Button(window,text="RESTARTUOTI SISTEMA",width = 30,command = restart_devices())
#button2.pack()
print("waiting...")
button.wait_variable(var)
Scanned_serial = entry.get()
print("Entry string=",Scanned_serial)
var.set(0)
label2=Label(window,text = "Vykdoma operacija:")
label2.pack()
window.update()
window.after(1000,Full_operation(Scanned_serial,label2,window))
window.mainloop()
This is my code. As you can see. i call Full_operation function and then window.mainloop()
my Full_operation:
def Full_operation(Scanned_serial,label2,window):
operacijos_kodas=Scanning_operation(myConnection,Scanned_serial)
print("operacijos kodas=",operacijos_kodas)
if(operacijos_kodas == 0):
label2.config(text = "SPAUSKITE MYGTUKA ANT DEZES KURIA NORITE PILDYTI:")#update the label2
window.update()#call update to update the label
pildymo_operacija(myConnection,Scanned_serial,label2,window)
elif(operacijos_kodas == 1):
insertData_komplektacija(myConnection,"fmb110bbv801.csv");
update_current_operation(myConnection);
#label2.config(text = "IMKITE DAIKTUS IS ZALIOS DEZUTES:")#update the label2
picking_operation(myConnection,label2);
elif(operacijos_kodas == 2):
print("Skenuokite dar karta")
label2.config(text = "NUSKENUOTAS NEGALIMAS KODAS:")#update the label2
window.update()#call update to update the label
How can I ensure that everytime I enter FUll_operation function I start from clean GUI again and start another operation.
Now I am able to complete operation once. After that, the GUI is not responsive.
I have added a print statement at the beggining of my full_operation and it does not execute after I complete it once so my mainwindow does not seem to work properly.
You'll need to adapt your code to work with a GUI. You can't introduce infinite loops in to tkinter GUI's without causing all sorts of problems.
Mainloop should only be called once.
I'd suggest that you move all of your scanning/saving operations in to a separate function which you schedule to occur periodically using the tkinter after method.
For example if you call your function scan you would schedule it to occur after 1 second using
root.after(1000, scan)
A more advanced method would be to have your scanning code running on a separate thread.
Also, you are currently trying to create the label each time you go round the while loop rather than just creating and packing them once and updating the text of the labels when you perform the "scanning". You can update the text of a label using the config method, for example
## Create a label
label1 = tk.Label(window,text = "PAKAVIMO OPERACIJA:")
##Pack the label
label1.pack()
## Update the text later
label1.config(text="New Text")
Here is an example of updating tkinter widgets periodically from a function.
import tkinter as tk
import random
def scanning():
num = random.randint(0,100)
entryTemperature.delete(0, tk.END) #Delete the current contents
entryTemperature.insert(0, f"{num} K") #Add new text
root.after(1000, scanning) #Schedule the function to run again in 1000ms (1 second)
root = tk.Tk()
entryTemperature = tk.Entry(root)
entryTemperature.grid(padx=50,pady=50)
root.after(1000, scanning)
root.mainloop()
When trying to display text overtime on the tkinter window through a button press, the label does not update correctly. After testing multiple different scenarios, the label is classified as visible, the text value for it is correct, the function itself works without a button press and no matter if using pack, grid or place the result is the same. No matter how the label is updated, it is never displayed on the screen, but only if the button calls the function. When the button is pressed, it runs through the function, printing out everything along the way, and therefore the button itself is also working. Code below:
def label_change(self, text):
""" Repeats f|char_show (with a pause) until <text> equals the label text.
Parameters:
text (str): The text displayed in the label.
"""
if self.last_txt == text:
pass
def char_show():
""" Adds the next single character of <text> to the label text.
Curtosy of Hritik Bhat on:
stackoverflow.com/questions/56684556/how-to-have-variable-text-in-a-label-in-tkinter
"""
if self.label_store == "":
self.label_index = 0
self.label_store += text[self.label_index]
print(self.label_index)
self.label_index += 1
self.display.place_forget()
self.display.config(text=self.label_store)
self.display.update()
self.display.place()
print(self.display.cget("text"))
self.display.update_idletasks()
self.label_change(self.txt)
If you want to add char with delay then you could use root.after(time_ms, function_name) to execute function with delay. This doesn't stops mainloop() and it doesn't need update() and `update_idletask()
import tkinter as tk
def start():
global label_index
# add next char to label
display['text'] += text[label_index]
label_index += 1
# check if there is need to run it again
if label_index < len(text):
# run again after 200ms
root.after(200, start)
# ---
text = 'Hello World'
label_index = 0
root = tk.Tk()
display = tk.Label(root)
display.pack()
button = tk.Button(root, text="Start", command=start)
button.pack()
root.mainloop()
EDIT: this version blocks button when it updates label. It also reset settings when it starts animation again.
import tkinter as tk
def add_char():
global label_index
global running
# add next char to label
display['text'] += text[label_index]
label_index += 1
# check if there is need to run it again
if label_index < len(text):
# run again after 200ms
root.after(200, add_char)
else:
# unblock button after end
running = False
def start():
global label_index
global running
# check if animation is running
if not running:
# block button
running = True
# reset settings
display['text'] = ''
label_index = 0
# run animation
add_char()
# ---
text = 'Hello World'
label_index = 0
running = False
root = tk.Tk()
display = tk.Label(root)
display.pack()
button = tk.Button(root, text="Start", command=start)
button.pack()
root.mainloop()
So i know that in the python window you can use this..
for char in introstring:
sleep(0.2)
sys.stdout.write(char)
sys.stdout.flush()
and it will display the text in the window, character by character at the speed of 0.2
How can I transition this into a Tkinter window? For example, I have the the text:
canvas.create_text((720,454),text="Just in case you start to feel sniffily, or something",fill="white", font=('arial'))
Is there a way to get the same animation but in the Tkinter GUI, I have set up? Ive heard something about the .after command, but I cant seem to find how it would apply for this.
Here's a very simple example that prints text character by character to a canvas, with a delay of 500 milliseconds between characters.
import Tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.pack()
canvas_text = canvas.create_text(10, 10, text='', anchor=tk.NW)
test_string = "This is a test"
#Time delay between chars, in milliseconds
delta = 500
delay = 0
for i in range(len(test_string) + 1):
s = test_string[:i]
update_text = lambda s=s: canvas.itemconfigure(canvas_text, text=s)
canvas.after(delay, update_text)
delay += delta
root.mainloop()
This code has been tested on Python 2.6. To run it on Python 3 you need to change the import statement to import tkinter as tk
Here's a more sophisticated example that displays text typed into an Entry widget. The text is shown when Enter is pressed in the Entry widget.
#!/usr/bin/env python
''' animate text in a tkinter canvas
See http://stackoverflow.com/q/34973060/4014959
Written by PM 2Ring 2016.01.24
'''
import Tkinter as tk
class Application(object):
def __init__(self):
root = tk.Tk()
self.canvas = tk.Canvas(root)
self.canvas.pack()
self.canvas_text = self.canvas.create_text(10, 10, text='', anchor=tk.NW)
self.entry = tk.Entry(root)
self.entry.bind("<Return>", self.entry_cb)
self.entry.pack()
root.mainloop()
def animate_text(self, text, delta):
''' Animate canvas text with a time delay given in milliseconds '''
delay = 0
for i in range(len(text) + 1):
update_text = lambda s=text[:i]: self.canvas.itemconfigure(self.canvas_text, text=s)
self.canvas.after(delay, update_text)
delay += delta
def entry_cb(self, event):
self.animate_text(self.entry.get(), 250)
app = Application()
from Tkinter import *
import time
while True:
for message in "Hello World":
time.sleep(.5)
root = Tk()
lb = Listbox(root, height=3)
lb.pack()
lb.insert(END, message)
lb.insert(END,"second entry")
lb.insert(END,"third entry")
root.mainloop()
You have to use root.after to call function which add single letter, and then it use root.after to call the same function to add next letter.
from Tkinter import *
# --- functions ---
def add_letter(text):
if text: # if text not empty
# add first letter from text
lb.insert(END, text[0])
# call again without first letter
root.after(500, add_letter, text[1:])
# --- main ---
root = Tk()
lb = Listbox(root, height=15)
lb.pack()
# first call after 500ms
root.after(500, add_letter, "Hello World")
root.mainloop()
EDIT: moving text :) using Text and reversed text
from Tkinter import *
# --- functions ---
def add_letter(text):
if text: # if text not empty
# add first letter from text
lb.insert('1.0', text[0]) # put at the beginning of line
# call again without first letter
root.after(250, add_letter, text[1:])
# --- main ---
root = Tk()
lb = Text(root)
lb.pack()
text = ''.join(reversed("Hello World of Python and Tkinter"))
# first call after 250ms
root.after(250, add_letter, text)
root.mainloop()
Your code never progresses beyond the while True: loop. So the rest of the code that creates the windows and widgets is not executed, hence no window appears.
Try removing the while loop:
from Tkinter import *
import time
for message in "Hello World":
time.sleep(.5)
root = Tk()
lb = Listbox(root, height=3)
lb.pack()
lb.insert(END, message)
lb.insert(END,"second entry")
lb.insert(END,"third entry")
root.mainloop()
Now your code will loop over the character in the string, sleeping 0.5 seconds for each character. Then the window and widgets will be created and displayed.
The code posted would be stuck in an infinite loop (the while True) sleeping over and over for .5 seconds per character in "Hello World"
The first issue is the While and sleep, both of which are unnecessary. If you want the letters, you're on the right track, since string are lists of char in Python
I believe you want something like the following:
import Tkinter
root = Tkinter.Tk()
lb = Tkinter.Listbox(root, height=3)
lb.pack()
for message in "Hello World":
lb.insert(Tkinter.END, message)
lb.insert(Tkinter.END, "second last entry")
lb.insert(Tkinter.END, "THE last entry")
lb.config(height=20)
root.mainloop()
Notice the config resize, since otherwise we can only see the elements beyond H e l if we click into the list and scroll using arrow keys. The following could help you understand how to get the listbox to resize :)
How to fit Tkinter listbox to contents