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()
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'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()
The current code displays a GUI with the listbox after all 3 elements have been added to the list. Is it possible to do it more interactively i.e. the GUI and the listbox are displayed immediately after the program starts, then every 1s a new element is added to the listbox? I would like to have a solution without parallelism, threads and synchronisation.
from Tkinter import *
import time
master = Tk()
listbox = Listbox(master)
listbox.pack()
for i in range(0,3):
#Time consuming task which results are placed sequentially in the listbox
time.sleep(1)
listbox.insert(END,"Task "+str(i)+" completed")
#GUI update needed here
#.....???
mainloop()
I tried to use after method like in the second listing, however the listbox is still not displayed until all the items are added to it.
from Tkinter import *
import time
def time_consuming_task():
for i in range(0,3):
time.sleep(1)
listbox.insert(END,"Task "+str(i)+" completed")
master = Tk()
listbox = Listbox(master)
listbox.pack()
master.after(100,time_consuming_task)
mainloop()
You can simply use after() as below:
import Tkinter as tk
master = tk.Tk()
listbox = tk.Listbox(master)
listbox.pack()
def time_consuming_task(n=0):
if n < 3:
listbox.insert(tk.END, 'Task {0} completed'.format(n))
master.after(1000, time_consuming_task, n+1)
master.after(1000, time_consuming_task)
master.mainloop()
You need to keep calling the after method to set up a chain of calls:
from Tkinter import *
master = Tk()
def time_consuming_task():
listbox.insert(END, "Task " + str(time_consuming_task.i) + " completed")
time_consuming_task.i += 1
if time_consuming_task.i < 3:
master.after(1000, time_consuming_task)
listbox = Listbox(master)
listbox.pack()
time_consuming_task.i = 0
time_consuming_task()
mainloop()
Also it is vital that you don't call sleep() in your code, otherwise tkinter will hang for that time.
I have a Flask application that inserts some data in a database. While the insertion is in progress, I want a progress bar to show how many data were inserted (so, I use the determinate mode). Knowing that Flask and tkinter do not go together well proof, I decided to show the tkinter window in a separate thread, the main thread being the Flask application.
Here is my code so far
import tkinter as tk
from multiprocessing import Process
from tkinter.ttk import Progressbar, Button
import global_library
from application import config
from application.admin import add_match as a
from application.xml import create_string as cs
from application.xml import dl_xml_file as dl
from application.xml import xml_parsing as xp
def show_progress_window(low_end, high_end, match_count):
root = tk.Tk()
progress_bar = Progressbar(root, mode='determinate', orient='horizontal', length=200)
progress_bar['maximum'] = high_end - low_end
progress_bar['value'] = match_count
progress_bar.pack()
cancel_button = Button(root, text='Anulare')
cancel_button.pack()
root.mainloop()
def import_engine(low_end, high_end):
file = config['DEFAULT']['PROTECTED_RESOURCE_PATH']
for match_id in range(low_end, high_end + 1, 1):
p = Process(target=show_progress_window, args=(low_end, high_end, match_id - low_end))
p.start()
p.join()
params = cs.create_match_details_string(match_id)
dl.download_xml_file(file, params, global_library.details_savepath)
match_details = xp.parse_match_details_file(match_id)
a.add_a_match(match_details[0], match_details[1], match_details[2], match_details[3], match_details[4],
match_details[5], match_details[6], match_details[7], match_details[8], match_details[9],
match_details[10], match_details[11], match_details[12], match_details[13], match_details[14],
match_details[15], match_details[16])
When I run this part, the progress bar updates, but I have to manually close the window for the importing to begin. After each import, the window appears, with the progress bar showing... progress. Obviously, this is from mainloop(). As obvious, I cannot suppres this method.
Where am I mistaking? What should I do so as I don't have to manually close the window at each iteration?
Later edit: a very important fact is that tkinter must run in the main thread. If it doesn't, the progress bar will not update no matter what.
Okay, my first answer made no sense. Have you tried taking the mainloop out of the function. Because now each time you call on the progress bar, it creates a new tk() instance.
something lke this:
root = tkinter.Tk()
# call your function and pass "root" to it as well
root.mainloop()
Edit 1:
The code below provides two functions illustrating that it seems you can 'update' tkinter by starting a new mainloop. But that's not the case.
import tkinter as tk
def looptk(min,max,progress):
root=tk.Tk()
label = tk.Label(root, text=progress).pack()
root.mainloop()
print(id(root))
def looptk2(min,max,progress,root):
label = tk.Label(root, text=progress).pack()
print(id(root))
min = 0
max = 6
i = 0
while i < max:
looptk(1,5,i)
i += 1
root=tk.Tk()
min = 0
max = 6
i = 0
while i < max:
looptk2(1,5,i,root)
i += 1
root.mainloop()
Edit 2:
Here is where you call the mainloop several times:
for match_id in range(low_end, high_end + 1, 1):
p = Process(target=show_progress_window, args=(low_end, high_end, match_id - low_end))
To fix it you need to create a mainloop before that for loop. Change it to something like this:
root = tk.tk()
for match_id in range(low_end, high_end + 1, 1):
p = Process(target=show_progress_window, args=(low_end, high_end, match_id - low_end,root))
root.mainloop()
Don't forget to add "root" between the brackets of the function show_progress_window(Add root to the end).
And remove the mainloop from the function.
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()