How to update the ttk progress bar on tkinter - python

I've got a program that does some simple webscraping. I'm working on giving it to my coworkers to use, but they are unfamiliar with code so I'd like to make a GUI. I've started using tkinter, and I'm currently working on making a progress bar showing where the program is at (it can take up to a couple hours to run). My problem is that I can't seem to get the progress bar to update, and all the online sources use Tkinter, which is an old version. Here is my code:
I've tried updating progressBar['value'] to whatever number I want, but that hasn't been working.
from tkinter import *
from tkinter import ttk
import time
def clicked(progressBar): # runs a couple functions and updates the progress bar when the button is clicked
num = 0
for item in range(5):
# functions go here
num += 10
progressBar['value'] = num
time.sleep(2)
window = Tk()
window.title("Welcome to my app")
window.geometry('600x400')
progressBar = ttk.Progressbar(window, orient='horizontal', length=300, mode='determinate', maximum=100, value=0)
progressBar.grid(columnspan=3, row=2, sticky=(W, E))
btn = Button(window, text="Click me!", command=clicked(progressBar))
btn.grid(column=1, row=1)
window.mainloop()
The tkinter window doesn't open up until 10 seconds after I run the program, and it has the progress bar already at 50% full. I'd like for the bar to slowly increment up, AFTER the button has been clicked. Any advice would be helpful! Thank you!

There are two problems with the code:
command=clicked(progressBar) indeed calls the function promptly.So simply use command=clicked. There is no need to pass progressBar as the argument since the function gets it from global scope.
while the function clicked() is running, GUI freezes. After 5*2sec, progressBar updates to 5*10 abruptly. To update the widgets in a loop, use update_idletastk method:
import tkinter as tk
from tkinter import ttk
import time
def clicked():
num = 0
for item in range(5):
num += 10
progressBar['value'] = num
window.update_idletasks()
time.sleep(2)
window = tk.Tk()
progressBar = ttk.Progressbar(window, orient='horizontal', length=300, mode='determinate', maximum=100, value=0)
progressBar.grid(columnspan=3, row=2, sticky=(tk.W, tk.E))
btn = tk.Button(window, text="Click me!", command=clicked)
btn.grid(column=1, row=1)
window.mainloop()

Related

How to update tkinter texbox in Python during program runtime

I am writing a Python GUI program using tkinter to read data from an instrument based on a list of setpoints and to show the results of each reading in a tkinter scrolled textbox as the program is running, once the user clicks the "Start" button.
However, my program only shows the results at the end of all loops, rather than update the textbox after each loop. In actual usage, the program might run for many tens of minutes due to the large number of test points.
I tried using the .after method, to allow the GUI to update, but the program does not update the textbox during each loop.
How can I modify my code so that the tkinter textbox in the GUI is updated during each loop?
Here is my simplified code using random results as a sample instrument reading:
import tkinter as tk
import tkinter.scrolledtext as tkscrolled
from tkinter import ttk
def start_measurement():
import random
test_points = [tp for tp in range(1,5)]
for setpoint in test_points:
data_reading = random.normalvariate(setpoint,0.05/2)
data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
data_results.after(1000)
data_results.insert(tk.INSERT,"\nDone\n")
root = tk.Tk()
# Create main frame for entire program: mainframe
mainframe = ttk.Frame(root, padding="10 10 10 10", style='bgstyle.TFrame')
mainframe.grid(row=0, column=0, sticky=(tk.N, tk.W, tk.E, tk.S))
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
# Create Data Output Text Box with Vertical Scrollbar
data_label = ttk.Label(mainframe, text="Results")
data_label.grid(row=1, column=0, padx=(30,30), pady=(20,0), sticky=(tk.S,tk.W,tk.E))
data_results = tkscrolled.ScrolledText(mainframe, width=100, height=20)
data_results.grid(row=2, column=0, padx=(30,30), pady=(0,20), sticky=(tk.N,tk.W,tk.E))
start_button = ttk.Button(mainframe, width=15, text="Start", command=lambda:start_measurement())
start_button.grid(row=0, column=0, padx=(30,30), pady=(20,0), sticky=(tk.N, tk.W))
root.mainloop()
data_results.after(1000) behaves like time.sleep(1). So the updates are not shown until tkinter mainloop() takes back the control after the function exits.
You should use .after() like below:
def start_measurement(setpoint=1):
import random
if setpoint < 5:
start_button.config(state=tk.DISABLED) # prevent multiple executions
data_reading = random.normalvariate(setpoint, 0.05/2)
data_results.insert(tk.INSERT, "Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
# execute this function one second later with updated setpoint
data_results.after(1000, start_measurement, setpoint+1)
else:
data_results.insert(tk.INSERT, "\nDone\n")
start_button.config(state=tk.NORMAL) # enable for next execution
You can use root.after like this -
data_results.after(1000,lambda:data_results.insert(tk.INSERT,"\nDone\n"))
So, after approximately 1 second it inserts Done. And you should keep this out of the for loop -
def start_measurement():
test_points = [tp for tp in range(1,5)]
for setpoint in test_points:
data_reading = random.normalvariate(setpoint,0.05/2)
data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
data_results.after(1000,lambda:data_results.insert(tk.INSERT,"\nDone\n"))
Change your code as follows.
def start_measurement():
import random
test_points = [tp for tp in range(1,5)]
for setpoint in test_points:
data_reading = random.normalvariate(setpoint,0.05/2)
data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
data_results.update()
data_results.after(1000)
data_results.insert(tk.INSERT,"\nDone\n")
Add data_results.update() to your code.This updates your textbox every second.After all iteration You get 'Done' displayed.

Python - tkinter button freeze

I have problem with freezing application in Python. I used tkinter library to make some app. When I using Send button it calls the functions that lasts 3 minutes and freezing application on this 3 minutes... I want to see all logs from this function "live". I can't wait 3 minutes and then get all the logs.
Below is example of my code:
import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
import time
class Frames:
def main_frame(self, win):
# Main Frame
main = ttk.LabelFrame(win, text="")
main.grid(column=0, row=0, sticky="WENS", padx=10, pady=10)
return main
def button_frame(self, win):
# Button Frame
butt_frame = ttk.LabelFrame(win, text="Button")
butt_frame.grid(column=0, row=0, sticky='NWS')
def _send_button():
for i in range(10):
self.write_text_console("{0}\n".format(i))
time.sleep(0.5)
# Send Button
ttk.Label(butt_frame, text=" ").grid(column=0, row=8)
button = tk.Button(butt_frame, text="Send", command=_send_button, foreground='black')
button.grid(column=0, row=13, sticky=tk.EW)
def scrolled_text_widget(self, win):
ttk.Label(win, text=" ").grid(column=0, row=1)
ttk.Label(win, text="Console Output:").grid(column=0, row=2, sticky=tk.W)
self.scr = scrolledtext.ScrolledText(win, width=100, height=10, wrap=tk.WORD, state="disabled")
self.scr.grid(column=0, row=3, columnspan=5)
def write_text_console(self, string, color="black", tag="console"):
self.scr.configure(state='normal')
self.scr.insert('end', string, tag)
self.scr.tag_config(tag, foreground=color)
self.scr.configure(state='disabled')
self.scr.see("end")
win = tk.Tk()
win.geometry("845x300")
win.resizable(0, 0)
frames = Frames()
main = frames.main_frame(win)
frames.button_frame(main)
frames.scrolled_text_widget(main)
win.mainloop()
This example showing my problem. When you click the Send button it freeze app for 5s. But I need to see the logs during the loop.
How can I resolve it?
Tkinter is running a loop in your main thread, that's why your app freezes when you click on a button. The solution is to create a new thread.
1- You have to import threading
import threading
2- Start a new thread in your _send_button() function. It should be like this.
def _send_button():
def click_button():
for i in range(10):
self.write_text_console("{0}\n".format(i))
time.sleep(0.5)
threading.Thread(target=click_button).start()
Learn More About Threading In Python
To freeze the button, add state= DISABLED in the specific button widget.
from tkinter import *
#Create an instance of tkiner frame
root= Tk()
#Define the geometry of the function
root.geometry("400x250")
Button(root, text="OK", state= DISABLED).pack(pady=20)
root.mainloop()

How to Avoid Blocking tkinter GUI while an infinite loop is running?

I made the following program to change screen brightness depending on the time of day. It uses an infinite loop to constantly check the time, but this also prevents the user from changing the values in the tkinter window or from closing it. Is there any way to avoid this?
from datetime import datetime
import screen_brightness_control as sbc
from tkinter import *
m=Tk()
m.title('Brightness Control')
def saver():
print("Saving")
global brit
brit=e3.get()
global frot
frot=e1.get()
global tot
tot=e2.get()
a=True
while a==True:
current_brightness=sbc.get_brightness()
now=datetime.now().time()
if now.hour>int(frot) and now.hour<int(tot) :
sbc.set_brightness(brit)
else:
sbc.set_brightness(40)
Label(m, text='Brightness').grid(row=0,column=0)
Label(m, text='From').grid(row=1,column=0)
Label(m, text='To').grid(row=1,column=2)
e1 = Entry(m)
e2 = Entry(m)
e3 = Entry(m)
e1.grid(row=1, column=1)
e2.grid(row=1, column=3)
e3.grid(row=0, column=1)
button = Button(m, text='Save', width=5,command=saver)
button.grid(row=2,column=3)
m.mainloop()
You have to add <tk.Tk object>.update() anywhere in your while loop like this:
import tkinter as tk
root = tk.Tk()
button = tk.Button(root, text="This is a button")
button.pack()
while True:
root.update()
root.update() updates tcl and that stops the "program isn't responding" message.

Tkinter: Updating progressbar when a function is called

Imagine the following simple example:
def doNothing():
sleep(0.5)
barVar.set(10)
sleep(0.5)
barVar.set(20)
sleep(0.5)
barVar.set(30)
mainWindow = Tk()
barVar = DoubleVar()
barVar.set(0)
bar = Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= Button(mainWindow, text='Click', command=doNothing)
button.grid(row=0, column=0)
mainWindow.mainloop()
What I get when I run this, the progressbar is already at 30% when clicking the button, no progress in front of me. Like attached:
What I need: I can see the progress in front of me (not hanging then suddenly 30%)
Update:
I upadted the code according to #Bernhard answer, but still I can not see the progress in front of me. Just a sudden jump of 30% after waiting 1.5 sec
Seocnd Update:
I'm only using sleep here as a simulation for a process that takes time, like connecting over ssh and grabing some info.
Do not use sleep() in tkinter. The entire reason for you problem is sleep() will freeze tkinter until it is done with its count so what you are seeing is a frozen program and when the program is finally released its already set to 30 percent on the next mainloop update.
Instead we need to use Tkinter's built in method called after() as after is specifically for this purpose.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+10)
mainWindow.after(500, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
If you want the bar to appear to move smoothly you will need to speed up the function call and reduce the addition to the DoubbleVar.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+0.5)
mainWindow.after(50, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
Because you are calling the function when the buttion is initialized, you need to loose the '(barVar') in the command=(barVar)). This way you bind the function to the button and don't call it when initializing it.
button= Button(mainWindow, text='Click', command=doNothing)
If you need to pass an argument you need to bypass the calling by using lambda:
button= Button(mainWindow, text='Click', command= lambda: doNothing(barVar))
I think I find the solution.
simply add mainWindow.update() after each progress. So the final code would be:
def doNothing():
sleep(0.5)
barVar.set(10)
mainWindow.update()
sleep(0.5)
barVar.set(20)
mainWindow.update()
sleep(0.5)
barVar.set(30)
mainWindow.update()

Tkinter (Python) output random numbers to GUI

Noob Alert!!!
Hello, I just started my journey with through python a couple of days ago, so my question will most likely be extremely simple to answer. Basically I have a random number gen. from 1 to 10, this is activated when the "test button is pressed on the windows that pops up. As you can see from the image below the random number appears in the output console on the bottom of the screen, in this case it was a 9. So here's the question, How can I make the random number on the GUI? so when the button is pressed a random number appears on the same window as the button.
https://i.stack.imgur.com/hWd3i.png
Any help is appreciated!
from tkinter import *
root = Tk()
root.geometry("300x300")
root.title("test it is")
root.grid()
def randnum(event):
import random
value =random.randint(1,10)
print(value)
button_1 = Button(root, text="test")
button_1.bind("<Button-1>",randnum)
button_1.pack()
root.mainloop()
from tkinter import *
root = Tk()
root.geometry("300x300")
root.title("test it is")
root.grid()
def randnum(event):
import random
value =random.randint(1,10)
print(value)
updateDisplay(value)
def updateDisplay(myString):
displayVariable.set(myString)
button_1 = Button(root, text="test")
button_1.bind("<Button-1>",randnum)
button_1.pack()
displayVariable = StringVar()
displayLabel = Label(root, textvariable=displayVariable)
displayLabel.pack()
root.mainloop()
Here is what it looks like.You have to create a Label with a Button, whose value will get updated when you click on button.
import tkinter as tk
from random import randint
win = tk.Tk()
def test_button_click():
label_val.set(randint(1, 10))
my_button = tk.Button(win, text='Test Button',
command=test_button_click)
my_button.grid(column=0, row=0)
label_val = tk.IntVar()
my_Label = tk.Label(win, textvariable=label_val)
my_Label.grid(column=1, row=0)
win.mainloop()
This will achieve what you are requesting -- create a tk window, add a button and label, use the callback test_button_click to set the labels int var when the button is clicked.

Categories