How to Update a Label Inside While Loop in Tkinter

Update label continuously while accessing data from a for loop inside a button's callback function

While multi-threading can be a way out for tkinter apps, tkinter itself is not thread-safe, and events or calls into tkinter made from other threads may cause strange errors.

This can be run in a single thread - the catch is that tkinter won't , by default, update anything on screen, until your long run function is over and control is resumed to the tkinter loop.

However, all that is needed to update the screen at any time is to call the .update() method on your widgets, or top-level widgets.

Since your function do calculations, it would not be a nice design to hard-code "window.update()" calls inside it. Instead, a design where an optional "update" callback is passed into your "run" function will be more elegant, and allow the same function to be run with other interfaces as text user interface program in the terminal, other gui toolkit like Qt or GTK, or even a web application.

I'd love to write "working code" of this example, but since you did not post a self contained example, I can't take my time to write a tkinter UI with the labels to update from scratch.

As I was writing the snippet bellow I saw your "run" is supposed to be a method. Please post self contained minimal, complete, executable examples, not out of context functions, when asking questions. If you app is assembled as a class, then update may be a method, instead of a callback passed as paremeter as I described above.

The idea goes like:

import tkinter as tk

class Mistery:
def __init__(mistery_instance):

# code to create the main window assigned
# to "selfroot", the needed widgets, and an
# action that will trigger the "run"
# function
self.root = tkinter.Tk()
"""you did not show your code. A lot of
over 15 year old documentation suggest inheriting
your own class
from "tk.Tk" and build your app on that class -
that definetelly is not a good
thing to do: there are _hundreds_ of methods on
a tkinter class that would
name-clash with your methods and you would never
know it. Just associate
your widet as an
attribute as in the line above
"""
self.label1 = ...
self.label2 = ...
# code to create the remainder of the UI
# and at some point, create a callback to execute "self.run"
...



def update(self,a, b):
#code to set the display entries in label1 and label2
...
root.update() # <- this ensures the window is repainted with the new values


def run(self, update_interval=100):

b = []
a = []
self.ga = Ga()
for i in range(self.generations):

ga.run(self.v.get())
b.append(ga.best().fitness)
a.append(ga.fitness_average)
if i % update_interval == 0:
self.update(a, b)
self.graph(a,b)

m = Mistery()
tkinter.mainloop()

Updating Labels in Tkinter with for loop

Just use a distinct StringVar for each Label. Currently, you just pass the same one to all the labels, so when you update it they all update together.

Here's an example. You didn't give a fully runnable program, so I had to fill in the gaps.

from tkinter import Tk, Label, StringVar

root = Tk()

list1 = [1,2,3,4,5]
list2 = [6,7,8,9,10]

for v1, v2 in zip(list1, list2):

item_values = '{} {}'.format(v1, v2)
sv = StringVar()
lbl = Label(root, width="100", height="2",textvariable=sv).pack()

sv.set(item_values)

root.mainloop()

Python tkinter : loop in Label

here, try this:

from Tkinter import *
import time
root=Tk()

variable=StringVar()

def update_label():
i=0
while 1:
i=i+1
variable.set(str(i))
root.update()

your_label=Label(root,textvariable=variable)
your_label.pack()
start_button=Button(root,text="start",command=update_label)
start_button.pack()
root.mainloop()

That should give you a good example. However, it is important to note that during the while loop, you MUST call root.update() otherwise your GUI will freeze until the loop completes (in this case it never does) and never show your numbers.

Also note that you can call update_label() from anywhere in your program. I just added it to the start button for example purposes.

What was wrong with your code was that you had set the while loop free-floating and most importantly before your GUI's mainloop. When you do this, since this loop is infinate, it never allows Tkinter to start its mainloop(). However, if you were to put the while loop after the mainloop, then that would never be executed until after you exit the GUI, this is because the mainloop is infinate until it is stopped (closing the GUI).

So to fix this you simply put it in a function and call it later on during Tkinter's mainloop. You can do this various ways as well, for example, you can use .after() to perform a specific task after a certain amount of time, or make it the command of a button to be run when pressed, ect., ect. .

However, The proper code you should use is the following, as you do not really want infinate loops in your code (other then you mainloop).:

class App (object):
def __init__(self):
self.root=Tk()
self.variable=StringVar()
self.i=0
self.your_label=Label(self.root,textvariable=self.variable)
def grid(self):
self.your_label.pack()
def update_label(self):
self.i=self.i+1
self.variable.set(str(self.i))
self.root.after(20,self.update_label)
def run(self):
self.grid()
self.root.after(20,self.update_label)
self.root.mainloop()

if __name__=='__main__':
App().run()

updating tkinter label while another loop runs

You should use a thread to scrape the page else it will block it.

This code worked for me,(Although it need to waste some time,I have add annotataion in my code):

import tkinter as tk
from tkinter import messagebox
import re,sys
from urllib.parse import urlparse
import requests,time
from bs4 import BeautifulSoup
import threading

class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Quotes Scraper v1")
self.geometry("400x250")
button = tk.Button(self, text="Collect Links",bd=3,
activebackground="grey90",command=self.start_thread) # start a thread instead of starting scrap the page.
button.place(x=130,y=120)

def start_thread(self):
threading.Thread(target=self.start_collecting).start() # create a thread to scrap a page in the background

def start_collecting(self): # this is your work.
url = "http://quotes.toscrape.com/"
res = requests.get(url)
if res.status_code != 200:
sys.exit('Check Internet')

self.clean_screen_plus()
quotes = []
while True:
soup = BeautifulSoup(res.text, 'lxml')
self.update_status()
quotes += [i.span.text.strip() for i in soup.findAll('div', {'class': 'quote'})]
try:
next_page = 'http://quotes.toscrape.com/' + soup.find('li', {'class': 'next'}).a['href']
time.sleep(5)
res = requests.get(next_page)
except AttributeError:
break
self.destroy()

def clean_screen_plus(self):
for widget in self.winfo_children():
widget.destroy()
self.geometry("300x100")
self.resizable(False, False)
self.status = tk.Label(self, text="Collecting")
self.status.grid()

def update_status(self):

current_status = self.status["text"]
if current_status.endswith("..."):
current_status = "Collecting"

else:
current_status += "."

# Update the message
self.status["text"] = current_status
self.update_idletasks()
self.after(1000, self.update_status) #updates every 1 sec
print(current_status)
App().mainloop()


Related Topics



Leave a reply



Submit