How to update Tkinter Progressbar? - python

So I'm writing some code to check VAT-IDs using a web API. Since some files have quite a large amount of API-calls it sometimes takes quite a while to complete and I want to show a progressbar so other users know that the program hasn't crashed yet. I found an example and modified it slightly so that it fits my needs. This code shows a window with a progressbar and once the for loop is finished the window closes.
import tkinter as tk
from tkinter import ttk
import time
def wrap():
MAX = 30000
root = tk.Tk()
root.geometry('{}x{}'.format(400, 100))
progress_var = tk.IntVar() #here you have ints but when calc. %'s usually floats
theLabel = tk.Label(root, text="Sample text to show")
theLabel.pack()
progressbar = ttk.Progressbar(root, variable=progress_var, maximum=MAX)
progressbar.pack(fill=tk.X, expand=1)
def loop_function():
k = 0
for k in range(MAX):
### some work to be done
progress_var.set(k)
time.sleep(0.002)
root.update()
root.destroy()
root.after(100, loop_function)
root.mainloop()
wrap()
Now I wanted to implement this into my tool:
import pandas as pd
import re
import pyvat
from tkinter import ttk
import tkinter as tk
def vatchecker(dataframe):
#initialise progressbar
root = tk.Tk()
root.geometry('{}x{}'.format(400, 100))
progress_var = tk.DoubleVar() #here you have ints but when calc. %'s usually floats
theLabel = tk.Label(root, text="Calling VAT Api")
theLabel.pack()
maxval = len(dataframe['Vat-ID'])
progressbar = ttk.Progressbar(root, variable=progress_var, maximum=maxval)
progressbar.pack(fill=tk.X, expand=1)
checked =[]
def loop_function():
for row in range(len(dataframe['Vat-ID'])):
print("Vatcheck: " + str(round(row/maxval * 100, 2)) + " %")
if pd.isna(dataframe['Vat-ID'][row]):
checked.append('No Vat Number')
else:
#check if vat id contains country code
groups = re.match(r'[A-Z][A-Z]', dataframe['Vat-ID'][row])
if groups != None:
querystring = dataframe['Vat-ID'][row][:-2]
country = dataframe['Vat-ID'][row][-2:]
#else get VAT-ID from Country ISO
else:
querystring = dataframe['Vat-ID'][row]
country = dataframe['Land-ISO2'][row]
try:
result = pyvat.check_vat_number(str(querystring), str(country))
checked.append(result.is_valid)
except:
checked.append('Query Error')
progress_var.set(row)
root.update()
root.destroy()
root.quit()
root.after(100, loop_function)
root.mainloop()
dataframe['Vat-ID-check'] = checked
return dataframe
This function gets called by the main script. Here the progressbar window is shown yet the bar doesn't fill up.
With print("Vatcheck: " + str(round(row/maxval * 100, 2)) + " %") I can still track the progress but it's slightly ugly.
Earlier in the main script the user already interacts with a Tkinter GUI but afterwards I close those windows and loops with root.destroy()' and 'root.quit() so I think it should be fine to run another Tkinter instance like this?
Any help or hints would be greatly appreciated.

as #jasonharper mentioned above changing progress_var = tk.DoubleVar() to progress_var = tk.DoubleVar(root) works

Related

Stop function doesn't work using progressbar

I'm writing a program in tkinter using Progressbar. But there is a problem when I added stop function it doesn't work. When I press "stop" button nothing happens, it should stop loading progressbar. I use Python version 3.8. The code below:
from tkinter import *
from tkinter import ttk
import time
root = Tk()
def run():
pb['maximum']=100
for i in range(101):
time.sleep(0.05)
pb['value']=i
pb.update()
def stop():
pb.stop()
runbutt = Button(root,text="Runprogr",command=run)
runbutt.pack()
stopbutt = Button(root,text="Stopbut",command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root,length=300,orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
The cause is that pb.stop couldn't stop the function in run.it will also increase by itself.
You could use .after(ms, callback) to add the value(then you no longer need to use time.sleep()).
If you want to stop it,use .after_cancel():
from tkinter import *
from tkinter import ttk
import time
root = Tk()
root.add_value = None
def run():
def add():
if pb['value'] >= 100:
return
pb['value'] += 1
root.add_value = root.after(50, add)
if root.add_value: # to prevent increasing the speed when user pressed "Runprogr" many times.
return
root.add_value = root.after(50, add)
def stop():
if not root.add_value: # to prevent raising Exception when user pressed "Stopbut" button many times.
return
root.after_cancel(root.add_value)
root.add_value = None
runbutt = Button(root, text="Runprogr", command=run)
runbutt.pack()
stopbutt = Button(root, text="Stopbut", command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root, length=300, orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()

Python tkinter Pop Up Progress Bar

I have created a simple program to download from a private azure container and rename a series of .jpg files listed in a csv file. I'm still learning python so I am sure the code is a bit on the rough side! That said, the code works fine and the files download correctly. However, I would like to display a pop up progress bar showing the current progress. I have looked at a number of examples but I'm not sure how best to approach this. Could anyone offer some pointers on the best way? Thanks.
from tkinter import messagebox
import urllib.request
import csv
from datetime import datetime, timedelta
from azure.storage.blob import BlockBlobService
from azure.storage.blob.models import BlobPermissions
from azure.storage.blob.sharedaccesssignature import BlobSharedAccessSignature
account_name = '***'
account_key = '***'
top_level_container_name = '***'
blob_service = BlockBlobService(account_name, account_key)
blob_shared = BlobSharedAccessSignature(account_name, account_key)
root = Tk()
root.withdraw()
csvDir = filedialog.askopenfilename(initialdir="/", title="SELECT CSV FILE", filetypes=(("CSV files", "*.csv"), ("all files", "*.*")))
imageDir = filedialog.askdirectory()
with open(csvDir) as images:
images = csv.reader(images)
img_count = 1
for image in images:
sas = blob_shared.generate_blob(container_name=top_level_container_name, blob_name=image[0], permission=BlobPermissions.READ, start=datetime.now(), expiry=datetime.now() + timedelta(hours=1))
sas_url = 'https://' + account_name + '.blob.core.windows.net' + '/' + top_level_container_name + '/' + image[0] + '?' + sas
print(sas_url)
urllib.request.urlretrieve(sas_url, imageDir + "/{}.jpg".format(image[1]))
img_count += 1
messagebox.showinfo("Complete", "Print images have been downloaded")
root.mainloop()
The Base Code
The base code that this project uses as its baseline is Shichao's Blog Post, Progress/speed indicator for urlretrieve() in Python and my version of it is simply a tkinter wrapper is big props to Shichao for the amazing Blog Post.
The Tkinter Code
If you merely wish to see the code without the breakdown, you simply see it and copy and paste the given example:
import time
import urllib.request
import tkinter as tk
import tkinter.ttk as ttk
class download_toplevel:
def __init__(self, master):
self.master = master
self.download_button = tk.Button(self.master, text="Download", command=self.download)
self.download_button.grid(row=0, column=0)
self.root = tk.Toplevel(self.master)
self.progress_bar = ttk.Progressbar(self.root, orient="horizontal",
length=200, mode="determinate")
self.progress_bar.grid(row=0, column=0)
self.progress_bar.grid_rowconfigure(0, weight=1)
self.progress_bar.grid_columnconfigure(0, weight=1)
self.progress_bar["maximum"] = 100
self.root.withdraw()
# See https://blog.shichao.io/2012/10/04/progress_speed_indicator_for_urlretrieve_in_python.html
def reporthook(self, count, block_size, total_size):
print(count, block_size, total_size)
if count == 0:
self.start_time = time.time()
return
duration = time.time() - self.start_time
progress_size = int(count * block_size)
speed = int(progress_size / (1024 * duration))
percent = min(int(count*block_size*100/total_size), 100)
print(percent)
self.progress_bar["value"] = percent
self.root.update()
def save(self, url, filename):
urllib.request.urlretrieve(url, filename, self.reporthook)
def download(self):
self.progress_bar["value"] = 0
self.root.deiconify()
self.save("https://files02.tchspt.com/storage2/temp/discord-0.0.9.deb", "discord-0.0.9.deb")
self.root.withdraw()
def main():
root = tk.Tk()
#root.withdraw()
downloader = download_toplevel(root)
root.mainloop()
if __name__ == '__main__':
main()
The Breakdown
The imports
import time
import urllib.request
import tkinter as tk
import tkinter.ttk as ttk
The import of tkinter.ttk is important as the Progress Bar widget is not found in the default tkinter module.
The main loop
def main():
root = tk.Tk()
downloader = download_toplevel(root)
root.mainloop()
if __name__ == '__main__':
main()
The line if __name__ == '__main__' means if the code is run directly without being imported by another python program execute the following statement, so on you running $ python3 main.py, the code will run the main function.
The main function works by creating the main window root, and passing the window into a variable within the class download_toplevel. This is a neater way of writing python code and is more versatile when working with tkinter as it simplifies the code when looking at it in the future.
The Download Button
self.master = master
self.download_button = tk.Button(self.master, text="Download", command=self.download)
self.download_button.grid(row=0, column=0)
This code adds a button labelled download bound to the command download
The Toplevel Window
self.root = tk.Toplevel(self.master)
This creates a toplevel window that appears on top of the master window
The Progress Bar
self.progress_bar = ttk.Progressbar(self.root, orient="horizontal",
length=200, mode="determinate")
self.progress_bar.grid(row=0, column=0)
self.progress_bar.grid_rowconfigure(0, weight=1)
self.progress_bar.grid_columnconfigure(0, weight=1)
self.progress_bar["maximum"] = 100
This creates a ttk.Progressbar onto the Toplevel window, oriented in the x plane, and is 200px long. The grid_*configure(0, weight=1) means that the progress bar should grow to fit the given space, to make sure this happens you should also include , sticky='nsew' in the grid command. self.progress_bar["maximum"] = 100 means that the max value is 100%.
Withdraw
self.root.withdraw()
This withdraws the Toplevel window until it is actually required, i.e. when the download button is pressed.
The Report Hook
def reporthook(self, count, block_size, total_size):
print(count, block_size, total_size)
if count == 0:
self.start_time = time.time()
return
duration = time.time() - self.start_time
progress_size = int(count * block_size)
speed = int(progress_size / (1024 * duration))
percent = min(int(count*block_size*100/total_size), 100)
This code is taken directly from Shichao's Blog Post, and simply works out the percentage completed.
self.progress_bar["value"] = percent
self.root.update()
The Progress Bar is then set to the current percentage and the Toplevel is updated, otherwise it remains as a black window (tested on Linux).
The save function
def save(self, url, filename):
urllib.request.urlretrieve(url, filename, self.reporthook)
This bit of code has been straight up taken from the Blog Post and simply downloads the file and on each block being downloaded and written the progress is sent to the self.reporthook function.
The Download Function
def download(self):
self.progress_bar["value"] = 0
self.root.deiconify()
self.save("https://files02.tchspt.com/storage2/temp/discord-0.0.9.deb", "discord-0.0.9.deb")
self.root.withdraw()
The download function resets the Progress Bar to 0%, and then the root window is raised (outside of the withdraw). Then the save function is run, you may wish to rewrite the self.save call to read self.after(500, [the_function]) so that your main window is still updated as this program is run. Then the Toplevel window is withdrawn.
Hope this helps,
James

How can I get the option selected by a user from a combobox in toplevel

I'm new to Python and Tkinter and was trying to create an interface to search & plot data. I created a very simple toplevel window to get the values from a combobox that would be selected from users. However, I find the script would only print the first item in the list if comboxlist2.current(0) was set or it would print nothing, no matter which one is selected in the box. I created a sample script to test this. If I click on the "search & create", then the return values can change according to the user selection in comboxlist1, while it would all return "1" no matter what the user selected in comboxlist2. So may I ask where is the issue and how to solve?
Thanks in advance for the potential suggestions or solutions!
import tkinter as tk
from tkinter import ttk
from tkinter import *
def root_print():
reg_in = comboxlist1.get()
print(reg_in) #print the value selected
def on_click():
tl = Toplevel()
comvalue2 = tk.StringVar()
comboxlist2 = ttk.Combobox(tl,textvariable=comvalue2)
comboxlist2["values"] = ("1","2","3")
comboxlist2.grid()
comboxlist2.current(0) #select the first one as default
#mm = comboxlist2.get()
#print(mm) #print directly
go(comboxlist2,tl)
tl.wait_window()
return
def go(comboxlist2,tl):
mm = comboxlist2.get()
Button(tl,text='go', command=lambda:test(mm)).grid()
def test(mm):
print(mm) #do the same thing for the comboxlist2
root = Tk()
root.title('search') #create an interface
root.geometry('+400+200') #size and position
Label(text='region ').grid(row=2,column=0)
comvalue1 = tk.StringVar()
comboxlist1=ttk.Combobox(root,textvariable=comvalue1)
comboxlist1["values"]=("all","africa","asia","australia","canada","europe","mexico","southamerica","usa")
comboxlist1.grid(row=2,column=1)
comboxlist1.current(0)
Button(text='search & create', command=root_print).grid(row=0,column=4)
Button(text='click', command=on_click).grid(row=1, column=4)
loop = mainloop()#go!
Here is the working code, which should take care of your needs. I have removed the imports and some code snippets which are not useful.
import tkinter as tk
from tkinter import ttk
def root_print():
reg_in = comboxlist1.get()
print(reg_in)
def on_click():
tl = tk.Toplevel()
comvalue2 = tk.StringVar()
comboxlist2 = ttk.Combobox(tl,textvariable=comvalue2)
comboxlist2["values"] = ("1","2","3")
comboxlist2.grid()
comboxlist2.current(0) #select the first one as default
tk.Button(tl,text='go', command=lambda: test(comboxlist2.get())).grid()
tl.wait_window()
def test(mm):
print(mm)
root = tk.Tk()
root.title('search') #create an interface
root.geometry('+400+200') #size and position
tk.Label(text='region ').grid(row=2,column=0)
comvalue1 = tk.StringVar()
comboxlist1=ttk.Combobox(root,textvariable=comvalue1)
comboxlist1["values"]=("all","africa","asia","australia","canada","europe","mexico","southamerica","usa")
comboxlist1.grid(row=2,column=1)
comboxlist1.current(0)
tk.Button(text='search & create', command=root_print).grid(row=0,column=4)
tk.Button(text='click', command=on_click).grid(row=1, column=4)
root.mainloop()

Rapidly Display Images With Tkinter

I am looking for an efficient way to rapidly display images with tkinter, and I mean like really fast. Currently I have this code:
from tkinter import*
import threading
import time
root = Tk()
root.geometry("200x200")
root.title("testing")
def img1():
threading.Timer(0.2, img1).start()
whitei = PhotoImage(file="white.gif")
white = Label(root, image=whitei)
white.image = whitei
white.place(x=0, y=0)
def img2():
threading.Timer(0.2, img2).start()
blacki = PhotoImage(file="black.gif")
black = Label(root, image=blacki)
black.image = blacki
black.place(x=0, y=0)
img1()
time.sleep(0.1)
img2()
root.mainloop()
Essentially the code just displays a black and white image but it puts my CPU at 100% usage and is pretty slow no matter how small I make the amount of time each picture is displayed for. Is there a faster, more efficient way to do this?
As mentioned, I would suggest using after. You aren't really supposed to alter any tkinter objects outside your main thread. Also, creating a new object each time isn't the most efficient. Here's something I would try:
import tkinter as tk
root = tk.Tk()
root.geometry("200x200")
root.title("testing")
whitei = tk.PhotoImage(file="white_.gif")
blacki = tk.PhotoImage(file="black_.gif")
label = tk.Label(root, image=whitei)
label.image1 = whitei
label.image2 = blacki
label.place(x=0, y=0)
time_interval = 50
def img1():
root.after(time_interval, img2)
label.configure(image=whitei)
def img2():
root.after(time_interval, img1)
label.configure(image=blacki)
root.after(time_interval, img1)
root.mainloop()
You do not need to use threading. 2nd unless you are using sleep() inside of a separate thread you should never use sleep in a tkinter application. sleep() interrupts the mainloop and will cause tkinter to freeze up until sleep is finished. This is 99.9% of the time not what you want to do so here you should use after() for any timed events.
You can simple create each label for each image and then with a tracking variable raise the correct label to the top.
Here is a simple example.
from tkinter import *
root = Tk()
root.geometry("200x200")
root.title("testing")
current_image = ""
black_image = PhotoImage(file="black.gif")
white_image = PhotoImage(file="white.gif")
black_label = Label(root, image=black_image)
white_label = Label(root, image=white_image)
black_label.image = black_image
white_label.image = white_image
black_label.grid(row=0, column=0)
white_label.grid(row=0, column=0)
def loop_images():
global current_image, black_image, white_image
if current_image == "white":
black_label.tkraise(white_label)
current_image = "black"
else:
white_label.tkraise(black_label)
current_image = "white"
root.after(100, loop_images)
loop_images()
root.mainloop()

Python tkinter GUI freezing/crashing

from Tkinter import *
import tkFileDialog
import tkMessageBox
import os
import ttk
import serial
import timeit
import time
######################################################################################
class MyApp:
def __init__(self, parent):
########################################################
#Setup Frames
self.MiddleFrame = Frame(parent) #Middle Frame
self.MiddleFrame.pack()
#GLOBAL VARIABLES
self.chip_number = 0 #number of chip testing
###########################################
#Middle Frame setup
Label(self.MiddleFrame, text='Done').grid(row=8, column=1, sticky = E)
self.Done = Canvas(self.MiddleFrame, bg="yellow", width=10, height=10)
self.Done.grid(row=8, column=2)
Label(self.MiddleFrame, text='Chip Number:').grid(row=9, column=1, sticky = E)
#start button
self.button1 = Button(self.MiddleFrame,state=NORMAL, command= self.start_pre)
self.button1["text"]= "START"
self.button1.grid(row=1, column=2, sticky = E)
###########################################
#Action of Start Button
def start_pre(self):
x = 0
while x<10000:
self.start_button()
x=x+1
#Talking to Board
def start_button(self):
#increase chip count number and update
self.chip_number += 1
Label(self.MiddleFrame, text=str(self.chip_number)).grid(row=9, column=2, sticky = E)
#reset-yellow
self.reset_color()
print "Still Working", self.chip_number
self.Done.configure(background="green")
self.Done.update_idletasks()
###############################################################
#Color Boxes
#Reset
def reset_color(self):
self.Done.configure(background="yellow")
self.Done.update_idletasks()
###############################################################################################################
#Start Programs
root = Tk() #makes window
root.title("Interface")
myapp = MyApp(root) #this really runs program
root.mainloop() #keep window open
With my program, i first push the start button.
I will print "still working" and the GUi will update chip number and blink done light over and over. The start button go to function that will execute 10000 times. However after 3000 iterations, the gui freeze, but the program is still print "still working". How do I keep the gui from crashing?
There are many problems with your code. For one, this is fundamentally flawed:
while self.stop == True:
self.start_button()
time.sleep(0.5)
You simply can't expect a GUI to behave properly with code like that. As a general rule of thumb you should never have the main thread of a GUI call sleep. Causing sleep prevents the event loop from processing any events, including low level events such as requests to refresh the screen.
The use of sleep has been asked and answered many times on stackoverflow. You might find some of those questions useful. For example,
windows thinks tkinter is not responding
Python Tkinter coords function not moving canvas objects inside loop
How do widgets update in Tkinter?
Tkinter multiple operations
Python Tkinter Stopwatch Error
You have another problem that falls into the category of a memory leak. From that while loop, you call self.start_button() indefinitely. This happens about once a second, due to sleep being called for half a second in the loop, and another half a second in start_button.
Each time you call start_button, you create another label widget that you stack on top of all previous widgets in row 9, column 2. Eventually this will cause your program to crash. I'm surprised that it causes your program to fail so quickly, but that's beside the point.
My recommendation is to start over with a simple example that does nothing but update a label every second. Get that working so that you understand the basic mechanism. Then, once it's working, you can add in your code that reads from the serial port.
May I suggest that you start over with the following code? You can port in back to Python 2 if needed, but your program has been rewritten to use Python 3 and has been designed to use tkinter's ability to schedule future events with the after methods. Hopefully, you will find the code easier to follow.
import collections
import timeit
import tkinter
def main():
root = Application()
root.setup()
root.mainloop()
class Application(tkinter.Tk):
def setup(self):
mf = self.__middle_frame = tkinter.Frame(self)
self.__middle_frame.grid()
bf = self.__bot_frame = tkinter.Frame(self)
self.__bot_frame.grid()
self.__port_set = False
self.__chip_number = 0
self.__chip_pass_num = 0
self.__chip_fail_num = 0
self.__chip_yield_num = 0
self.__stop = True
self.__widgets = collections.OrderedDict((
('COT', 'Continuity Test'), ('CHE', 'Chip Erase'),
('ERT', 'Erase Test'), ('WRT', 'Write Test'),
('WIRT', 'Wire Reading Test'), ('WIT', 'Wire Reading Test'),
('WRAT', 'Write All Test'), ('DO', 'Done')))
for row, (key, value) in enumerate(self.__widgets.items()):
label = tkinter.Label(mf, text=value+':')
label.grid(row=row, column=0, sticky=tkinter.E)
canvas = tkinter.Canvas(mf, bg='yellow', width=10, height=10)
canvas.grid(row=row, column=1)
self.__widgets[key] = label, canvas
self.__cn = tkinter.Label(mf, text='Chip Number:')
self.__cn.grid(row=8, column=0, sticky=tkinter.E)
self.__display = tkinter.Label(mf)
self.__display.grid(row=8, column=1, sticky=tkinter.E)
self.__button = tkinter.Button(bf, text='START',
command=self.__start_pre)
self.__button.grid(sticky=tkinter.E)
def __start_pre(self):
self.__button['state'] = tkinter.DISABLED
self.__start_button(0)
def __start_button(self, count):
if count < 100:
self.__chip_number += 1
self.__display['text'] = str(self.__chip_number)
self.__widgets['DO'][1]['bg'] = 'yellow'
start_time = timeit.default_timer()
print('Still Working:', self.__chip_number)
self.after(500, self.__end_button, count)
else:
self.__button['state'] = tkinter.NORMAL
def __end_button(self, count):
self.__widgets['DO'][1]['bg'] = 'green'
self.after(500, self.__start_button, count + 1)
if __name__ == '__main__':
main()

Categories

Resources