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.
Related
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()
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()
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()