How to Create an Automatically Updating Gui Using Tkinter

How do I create an automatically updating GUI using Tkinter?

You can use after() to run function after (for example) 1000 miliseconds (1 second) to do something and update text on labels. This function can run itself after 1000 miliseconds again (and again).

It is example with current time

from Tkinter import *
import datetime

root = Tk()

lab = Label(root)
lab.pack()

def clock():
time = datetime.datetime.now().strftime("Time: %H:%M:%S")
lab.config(text=time)
#lab['text'] = time
root.after(1000, clock) # run itself again after 1000 ms

# run first time
clock()

root.mainloop()

BTW: you could use StringVar as sundar nataraj Сундар suggested


EDIT: (2022.01.01)

Updated to Python 3 with other changes suggested by PEP 8 -- Style Guide for Python Code

import tkinter as tk   # PEP8: `import *` is not preferred
import datetime

# --- functions ---
# PEP8: all functions before main code
# PEP8: `lower_case_name` for funcitons
# PEP8: verb as function's name

def update_clock():
# get current time as text
current_time = datetime.datetime.now().strftime("Time: %H:%M:%S")

# udpate text in Label
lab.config(text=current_time)
#lab['text'] = current_time

# run itself again after 1000 ms
root.after(1000, update_clock)

# --- main ---

root = tk.Tk()

lab = tk.Label(root)
lab.pack()

# run first time at once
update_clock()

# run furst time after 1000ms (1s)
#root.after(1000, update_clock)

root.mainloop()

Updating a Tkinter Gui in Python while running the program

First of all, next time you give us an example that we can run without having to modify the code. For example, the whole DB thing is not needed here. The problem is not with the DB but with the updating of the combobox.

The solution is very easy. The combobox does not reference your dropdownVals but rather copies it. So changhing the dropdownVals does not affect Tkinter. You have to supply the dropdownVals to the dropdownbox every time you update them:

from Tkinter import *
import csv, sqlite3, sys
#from Populate_DB import *
from ttk import Combobox

def List_bases():
"""sql = cur.execute('SELECT DISTINCT Base FROM Commodities')
data = cur.fetchall()
for i in data:
neat = ' '.join(map(str, (i)))
dropdownVals.append(neat)
"""
dropdownVals = []
for i in ["test1", "test2"]:
dropdownVals.append(i)
return dropdownVals

class App:
def __init__(self, master):

frame = Frame(master)
frame.pack()

dropdownVals = []

self.drop = Combobox(frame, values=dropdownVals)
self.drop.grid(row=1, column=2, sticky='w', padx=5, pady=5)

self.quitButton = Button(frame, text="QUIT", fg="red", cnf={},
command=quit)
self.quitButton.grid(row=5, column=0, sticky='w', padx=5, pady=5)

self.importButton = Button(frame, text="Import data",
command=self.de)
self.importButton.grid(row=1, column=0, sticky='w', padx=5, pady=5)

self.importCleanButton = Button(frame, text="Import clean",
command=self.de_c)
self.importCleanButton.grid(row=2, column=0, sticky='w', padx=5,
pady=5)

self.baseButton = Button(frame, text="Bases", command=self.base)
self.baseButton.grid(row=3, column=0, sticky='w', padx=5, pady=5)

def de(self):
data_entry()

def de_c(self):
data_entry_clean()

def base(self):
dropdownVals = List_bases()
self.drop.config(values=dropdownVals)
print "bases!"

root = Tk()
app = App(root)
root.mainloop()

Update Tkinter GUI from a separate thread running a command

In tkinter, you can submit an event from a background thread to the GUI thread using event_generate. This allows you to update widgets without threading errors.

  1. Create the tkinter objects in the main thread
  2. Bind the root to a vitual event (ie << event1 >>), specifying an event handler. Arrow brackets are required in the event name.
  3. Start the background thread
  4. In the background thread, use event_generate to trigger the event in the main thread. Use the state property to pass data (number) to the event.
  5. In the event handler, process the event

Here's an example:

from tkinter import *
import datetime
import threading
import time

root = Tk()
root.title("Thread Test")
print('Main Thread', threading.get_ident()) # main thread id

def timecnt(): # runs in background thread
print('Timer Thread',threading.get_ident()) # background thread id
for x in range(10):
root.event_generate("<<event1>>", when="tail", state=123) # trigger event in main thread
txtvar.set(' '*15 + str(x)) # update text entry from background thread
time.sleep(1) # one second

def eventhandler(evt): # runs in main thread
print('Event Thread',threading.get_ident()) # event thread id (same as main)
print(evt.state) # 123, data from event
string = datetime.datetime.now().strftime('%I:%M:%S %p')
lbl.config(text=string) # update widget
#txtvar.set(' '*15 + str(evt.state)) # update text entry in main thread

lbl = Label(root, text='Start') # label in main thread
lbl.place(x=0, y=0, relwidth=1, relheight=.5)

txtvar = StringVar() # var for text entry
txt = Entry(root, textvariable=txtvar) # in main thread
txt.place(relx = 0.5, rely = 0.75, relwidth=.5, anchor = CENTER)

thd = threading.Thread(target=timecnt) # timer thread
thd.daemon = True
thd.start() # start timer loop

root.bind("<<event1>>", eventhandler) # event triggered by background thread
root.mainloop()
thd.join() # not needed

Output (note that the main and event threads are the same)

Main Thread 5348
Timer Thread 33016
Event Thread 5348
......

I added an Entry widget to test if the StringVar can be updated from the background thread. It worked for me, but you can update the string in the event handler if you prefer. Note that updating the string from multiple background threads could be a problem and a thread lock should be used.

Note that if the background threads exits on its own, there is no error. If you close the application before it exits, you will see the 'main thread' error.

tkinter: How to properly update GUI as a result of separate process in TKinter?

Move the root.mainloop() line to the bottom of your file.

You also need to use the proper call in the next-to-last line and also in the function, like this:

root.after(100,updateConnectionStatus, tk,root,canvas1) 

You should also update the Label, rather than create a new one every time.

Update data in a Tkinter-GUI with data from a second Thread

This solution is based on comments from other person. It use queue.Queue to share data between the two threads. The Tkinter GUI/Thread use a 1-second-timer to check if new data is in the Queue and use it to refresh its Labels.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# based on <https://stackoverflow.com/a/47920128/4865723>

from tkinter import *
import asyncio
import threading
import random
import queue


class AsyncioThread(threading.Thread):
def __init__(self, the_queue, max_data):
self.asyncio_loop = asyncio.get_event_loop()
self.the_queue = the_queue
self.max_data = max_data
threading.Thread.__init__(self)

def run(self):
self.asyncio_loop.run_until_complete(self.do_data())

async def do_data(self):
""" Creating and starting 'maxData' asyncio-tasks. """
tasks = [
self.create_dummy_data(key)
for key in range(self.max_data)
]
await asyncio.wait(tasks)

async def create_dummy_data(self, key):
""" Create data and store it in the queue. """
sec = random.randint(1, 10)
data = '{}:{}'.format(key, random.random())
await asyncio.sleep(sec)

self.the_queue.put((key, data))


class TheWindow:
def __init__(self, max_data):
# thread-safe data storage
self.the_queue = queue.Queue()

# the GUI main object
self.root = Tk()

# create the data variable
self.data = []
for key in range(max_data):
self.data.append(StringVar())
self.data[key].set('<default>')

# Button to start the asyncio tasks
Button(master=self.root,
text='Start Asyncio Tasks',
command=lambda: self.do_asyncio()).pack()
# Frames to display data from the asyncio tasks
for key in range(max_data):
Label(master=self.root, textvariable=self.data[key]).pack()
# Button to check if the GUI is freezed
Button(master=self.root,
text='Freezed???',
command=self.do_freezed).pack()

def refresh_data(self):
"""
"""
# do nothing if the aysyncio thread is dead
# and no more data in the queue
if not self.thread.is_alive() and self.the_queue.empty():
return

# refresh the GUI with new data from the queue
while not self.the_queue.empty():
key, data = self.the_queue.get()
self.data[key].set(data)

print('RefreshData...')

# timer to refresh the gui with data from the asyncio thread
self.root.after(1000, self.refresh_data) # called only once!

def do_freezed(self):
""" Button-Event-Handler to see if a button on GUI works.
The GOAL of this example is to make this button clickable
while the other thread/asyncio-tasks are working. """
print('Tkinter is reacting. Thread-ID: {}'
.format(threading.get_ident()))

def do_asyncio(self):
"""
Button-Event-Handler starting the asyncio part in a separate
thread.
"""
# create Thread object
self.thread = AsyncioThread(self.the_queue, len(self.data))

# timer to refresh the gui with data from the asyncio thread
self.root.after(1000, self.refresh_data) # called only once!

# start the thread
self.thread.start()


if __name__ == '__main__':
window = TheWindow(10)
window.root.mainloop()

This example is based on https://stackoverflow.com/a/47920128/4865723.
Not sure if this is an elegant solution. Please feel free to edit this. It is my goal to make my question and the answer reusable by others.

How to update tkinter label text in real time

Assigning a variable to the text argument doesn't help, because even if the value of the variable changes, it will not be reflected in the label. Here is my approach to this (this is just one out of many possible ways)

import pyautogui, PIL
import tkinter as tk
from threading import Thread

def cursorpixel():
x,y = pyautogui.position()
pixel = (x,y,x+1,y+1)
grabColor(pixel)

def grabColor(square, max_colors=256):
global color_label,root
img=PIL.ImageGrab.grab(square)
color = img.getcolors(max_colors)
color_label.config(text=color)

def refresh():
while True:
cursorpixel()

def main():
global color_label,root
root=tk.Tk()
root.minsize(150, 50)
color_label = tk.Label(root,
fg = "black",
font = "Arial")
color_label.pack()
Thread(target=refresh).start()
root.mainloop()

if __name__ == "__main__":
main()

NOTES

  • I have used multi threading instead and created a function refresh() which triggers the cursorpixel() in an infinite loop.
  • I have called the grabColor() function from cursorpixel() having pixel as parameter.
  • I have used the color_label.config() method to change the text in the label, you could also use color_label['text'] or maybe assign a textvariable var = StringVar() to the label and then use var.set() on it.
  • I am not sure if it is a good choice to put the __name__='__main__' in a while loop as you will not be able to close the window without terminating the task, new one will pop up every time you try to do so.


Related Topics



Leave a reply



Submit