I created a small GUI using Tkinter. It has a button on click of which some data is written to Excel.
To show the progress, I added a progress bar but since the process is resource intensive, the progress bar appears only at the end. Therefore, I used threading as shown.
In the Main function below, I initialized the progress bar in a different thread but I want to update the current value of the task in Start function.
Because of this line:
progressbar.Start()
it is just continuously running without anything to do with the current progress.
def Start():
x = 0
progressbar["value"] = x
for idx, val in enumerate(rows):
region_url = val[1]
if (model_url != '-'):
url = 'http://testurl/' + region_url
x = x + 1
if (x > 4):
break
# Main
if __name__ == '__main__':
window = Tk()
new = progress(window)
# Add a grid
mainframe = Frame(window)
mainframe.grid(column=0,row=0, sticky=(N,W,E,S) )
mainframe.columnconfigure(0, weight = 1)
mainframe.rowconfigure(0, weight = 1)
mainframe.pack(pady = 100 , padx = 150)
# DropDown
popupMenu = OptionMenu(mainframe, tkvar, *regionList)
Label(mainframe, text="Region").grid(row = 1, column = 0)
# Button
btnSubmit = Button(mainframe, text= "Execute",command=StartScrap).grid(row = 2, column = 18)
popupMenu.grid(row = 2, column =0)
# Progress Bar
progressbar = ttk.Progressbar(window, orient = HORIZONTAL,length=300, mode = 'indeterminate')
progressbar.pack()
t = threading.Thread()
progressbar["maximum"] = 4
progressbar.start()
window.mainloop()
There are a couple things in your code that needs fixing.
Firstly you defined the function Start which controls how your progressbar is updated, but you never call it.
Secondly, you define the progressbar mode as indeterminate and that just start the progress bar - it works but it will just periodically move the progressbar.
Thirdly, you defined the Thread t but never put a target function.
Here's how to move your progressbar in a thread:
from tkinter import *
from tkinter import ttk
import threading
import time
def Start():
def update_pbar():
for i in range(5): #replace this with your method to update your progressbar
time.sleep(1)
progressbar["value"] = i
t = threading.Thread(target=update_pbar)
t.start()
# Main
if __name__ == '__main__':
window = Tk()
progressbar = ttk.Progressbar(window, orient = HORIZONTAL,length=300, mode = 'determinate')
progressbar.pack()
progressbar["maximum"] = 4
btnSubmit = Button(window, text="Execute", command=Start).pack()
window.mainloop()
Related
I am trying to add a progress bar to my window until some work is being done. But it is not working properly. I want it to keep moving until the work is done but it just moves rapidly and then stops. Also if I try to minimize or close the progress window it just hangs and stops responding.
Can anyone help me how can I do it properly? Here is my code.
import time
from tkinter import ttk
from tkinter import *
numbers = []
def main():
main_window = Tk()
app = info(main_window)
main_window.mainloop()
class info:
def __init__(self, root):
# start = timer()
self.error_str = ''
self.root1 = root
self.root1.title('LOADING......')
self.root1.geometry("380x200")
self.root1.eval('tk::PlaceWindow . center')
self.root1.resizable(width=False, height=False)
self.root1.configure(background='white')
progress = ttk.Progressbar(self.root1, orient=HORIZONTAL,
length=380, mode='determinate')
progress.place(x=0, y=100)
i = 20
for x in range(1, 50):
numbers.append(x * 2)
print(numbers)
progress['value'] = i
self.root1.update_idletasks()
time.sleep(0.1)
i = i + 40
self.root = root
self.root.title('Second window')
self.root.geometry('1350x800+0+0')
frame1 = Frame(self.root, bg='#7877a5')
frame1.place(x=0, y=0, width=1350, height=150)
title = Label(frame1, text="Second Window", font=("Times New Roman", 40, "bold", "italic"),
bg='#7877a5',
fg='white')
title.place(x=380, y=45)
if __name__ == '__main__':
main()
Generally speaking you shouldn't call time.sleep() in a tkinter application because it interferes with the GUI's mainloop() and will make your program hang or freeze. Use the universal widget method after() instead.
Lastly you need to specify a maximum value for the Progressbar so its indicator scales properly relatively to the values of i you are setting its value to. The default for maximum is only 100, which your code was greatly exceeding in the for x loop.
Here's the code that needs to change in info.__init__(). The two lines changed have # ALL CAPS comments:
progress = ttk.Progressbar(self.root1, orient=HORIZONTAL,
length=380, mode='determinate',
maximum=(48*40)+20) # ADDED ARGUMENT.
progress.place(x=0, y=100)
i = 20
for x in range(1, 50):
numbers.append(x * 2)
print(numbers)
progress['value'] = i
self.root1.update_idletasks()
self.root1.after(100) # Delay in millisecs. # REPLACED TIME.SLEEP() CALL.
i = i + 40
I am trying to create a progress bar that runs as long as my function is running to show the user that things are happening and not just frozen. My function (generate_reports) makes queries to the database and writes to CSV files. Here is an abstract version of my code:
from tkinter import *
from tkinter import ttk
from billing import generate_reports
class app:
def __init__(self, root):
self.mainframe = ttk.Frame(root, padding = '4 4 12 12')
self.mainframe.grid(column = 0, row = 0, sticky = (N, W, E, S))
ttk.Button(self.mainframe, text = "Generate Billing Reports", command = self.do_reports).grid(column = 2, row = 3, sticky = (W, E))
def do_reports(self, *args):
pbar = ttk.Progressbar(self.mainframe, orient = HORIZONTAL, mode = 'indeterminate')
pbar.grid(row = 4, column = 3, sticky = (W, E))
t1 = threading.Thread(target = generate_reports, args = [start, end])
t1.start()
pbar.start()
t1.join()
pbar.stop()
return
root = Tk()
BillingApp(root)
root.mainloop()
With this code, the progress bar doesn't pop up until after the generate_reports thread is completed and it is unmoving. If I remove the join, everything works fine but it never stops loading. How can I make the loading bar run for only the duration of the generate_reports thread?
Heh welcome to the fun world of event driven programming :). You can't use join here, the point of that function is to block until the thread is done and the whole point of using a thread is to avoid blocking the mainloop. You have 2 choices: either set up the GUI to constantly poll the thread to see if it's still running, or set up the thread to send a message back to the GUI when it's done. This latter option is probably the cleanest, and it's often done using tkinter's event mechanism.
from tkinter import *
from tkinter import ttk
import threading
import time
def generate_reports(start, end):
print("provide a mcve next time!")
time.sleep(5)
def run_report(root, *args):
generate_reports(*args)
root.event_generate("<<PhishDoneEvent>>") # yes, this is using tkinter in a thread, but some tkinter methods are ok to use in threads
class BillingApp:
def __init__(self, root):
self.mainframe = ttk.Frame(root, padding = '4 4 12 12')
self.mainframe.grid(column = 0, row = 0, sticky = (N, W, E, S))
ttk.Button(self.mainframe, text = "Generate Billing Reports", command = self.do_reports).grid(column = 2, row = 3, sticky = (W, E))
root.bind("<<PhishDoneEvent>>", self.report_done)
def do_reports(self, *args):
# note this makes a new widget with every click ... this is bad. Refactor to reuse the widget.
self.pbar = ttk.Progressbar(self.mainframe, orient = HORIZONTAL, mode = 'indeterminate')
self.pbar.grid(row = 4, column = 3, sticky = (W, E))
start, end = 4,5
t1 = threading.Thread(target = run_report, args = [root, start, end])
t1.start()
self.pbar.start()
def report_done(self, event=None):
self.pbar.stop()
Label(self.mainframe, text="report done").grid(row = 4, column = 3)
root = Tk()
BillingApp(root)
root.mainloop()
I see that you have accepted the answer above. However, I would post my answer since it is not based on threading, which I think is simpler and may be more suitable:
from tkinter import *
from tkinter.ttk import *
import os
root = Tk()
# I set the length and maximum as shown to demonstrate the process in the
# proceeding function
progress = Progressbar(root, orient = HORIZONTAL,
length = 200/5, maximum=200/5, mode = 'determinate')
# Function
def my_func():
t=0
r= 1/5
for i in range(200):
print(i)
t=t+r
progress['value'] = t
root.update_idletasks()
progress.pack()
# Button
Button(root, text = 'Start', command = bar).pack(pady = 10)
mainloop()
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 tried to implement a Tkinter progressbar using threading simply to see when a program is running, and to close the progressbar when the program ends.
import tkinter
import ttk
import time
import threading
def task(root):
ft = ttk.Frame()
ft.pack(expand=True, fill=tkinter.BOTH, side=tkinter.TOP)
pb_hD = ttk.Progressbar(ft, orient='horizontal', mode='indeterminate')
pb_hD.pack(expand=True, fill=tkinter.BOTH, side=tkinter.TOP)
pb_hD.start(50)
root.mainloop()
def process_of_unknown_duration(root):
time.sleep(5)
root.destroy()
def pBar():
root = tkinter.Tk()
t1=threading.Thread(target=process_of_unknown_duration, args=(root,))
t1.start()
task(root) # This will block while the mainloop runs
t1.join()
if __name__ == '__main__':
pBar()
#some function
My issue is that once the progressbar starts, the program just hangs and wont do anything else. Any hints?
This is because your call root.mainloop() is blocking the execution of your code. It basically represents the loop for your UI. You might want to look at this answer for a progress bar that is launched by a button.
Are you still interested in this problem? Try to use the update() method for the root object instead of the threading in the simple cases. I offer the following simplified demo-solution with global variables as a start point for subsequent developments.
import tkinter
from tkinter import *
from tkinter import ttk
import time
root1 = Tk()
progrBar1 = None # The main progress bar
win2 = None
def click_but1():
global win2, progrBar2
if win2 is None:
win2 = Toplevel() # Secondary window
win2.title('Secondary')
win2.protocol('WM_DELETE_WINDOW', clickClose) # close the secondary window on [x] pressing
but2 = Button(win2, text='Close', command=clickClose)
but2.pack()
but3 = Button(win2, text='Process', command=clickProcess)
but3.pack()
progrBar2 = ttk.Progressbar(win2, orient = 'horizontal', length = 300, mode = 'determinate')
progrBar2.pack()
if progrBar1:
progrBar1.start(50)
def clickClose():
global win2
progrBar1.stop()
win2.destroy()
win2=None
def clickProcess():
my_func()
def my_func():
global progrBar2
range1, range2 = 20, 40
step1 = 100/range1
for i in range(range1):
for j in range(range2):
time.sleep(0.01)
progrBar2.step(step1)
root1.update() # the "update" method
root1.title('Root') # Main window
progrBar1 = ttk.Progressbar(root1, orient = 'horizontal', mode = 'indeterminate') # The main progress bar
but1 = Button(root1, text = 'Start', command = click_but1)
but1.pack()
progrBar1.pack()
root1.mainloop()
The progress bar in the first (main) window is moving only in the presence of the secondary window. This pogress bar stops and returns to the initial position after closing of the secondary window. The secondary window has it own progress bar for the demo purposes and to show the interaction between windows by means of the update() method.
The first set of code works. It displays the updating variable. However, in the second code, when I put the variable in a Treeview widget instead, the variable just stays on 0 and does not update. How come this works with the label but not with treeview? What is the easiest way to fix this?
First (works):
from tkinter import *
from tkinter import ttk
import threading
import time
def fun():
for i in range(10):
var.set(var.get() + 1)
time.sleep(.5)
t = threading.Thread(target=fun)
root = Tk()
var = IntVar()
var.set(0)
mainframe = ttk.Frame(root)
mainframe.grid(column = 0, row = 0)
label = ttk.Label(mainframe, textvariable=var)
label.grid(column = 0, row = 0)
t.start()
root.mainloop()
Second (does not work):
from tkinter import *
from tkinter import ttk
import threading
import time
def fun():
for i in range(10):
var.set(var.get() + 1)
time.sleep(.5)
t = threading.Thread(target=fun)
root = Tk()
var = IntVar()
var.set(0)
mainframe = ttk.Frame(root)
mainframe.grid(column = 0, row = 0)
tree = ttk.Treeview(mainframe, columns = ('number'), height = 1)
tree.insert('', 'end', text = 'Number', values = var.get())
tree.grid(column=0, row=0)
t.start()
root.mainloop()
modify fun to :
def fun():
for i in range(10):
var.set(var.get() + 1)
x = tree.get_children()
tree.item(x, text = 'Number', values = var.get())
time.sleep(.5)
The get_children method returns a list of tuples item IDs, one for each child of the tree. With tree.item then update the child with the required id.
In the second program along with the variable var, you also have to update the child of the tree