How to update python tkinter window - python

I'm currently trying to update a TKinter window every second. So the idea is that is should open a window, let python updates the field, show updated window. The situation right now is that the second window is only showing when i close the first one. I'm guessing this has something to do with mainloop(). I looked into .update() and .update_idletasks() but I can't figure out how to implement it. TKinter is used to show a field with houses in it. So in general is should do this:
Generate house location (already implemented)
Show field with houses in it (already implemented)
Generate new location of houses (already implemented)
Show updated field
This is my current code. I'm not sure if the update function is necessary.
class Plot(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
def createWidgets(self, list_houses):
houses = list_houses
self.w = tk.Canvas(self, width = field.width*SIZE, height = field.height*SIZE, bg = '#E4E4E4')
...
self.w.grid()
def update(self):
?
#GENERATES FIELD AND HOUSES
...
#PRINT FIRST WINDOW
plot = Plot()
plot.createWidgets(houses) <- PUT HOUSES IN TK INTER
plot.master.title('map of houses')
plot.mainloop()
# UPDATE FIELD <- THIS PART IS ONLY EXECUTED WHEN I CLOSE THE FIRST WINDOW, WHY?
i = 0
while i < 2:
update = field.update_houses(houses) <- GENERATES NEW LOCATION OF HOUSES
#PRINT UPDATED WINDOW, IT SHOULD BE PRINTED IN THE SAME WINDOW!
plot = Plot()
plot.createWidgets(houses) <- PUT HOUSES IN TKINTER
plot.master.title('map of houses')
i += 1
Thanks in advance!

Your update field is only executed when you close the window because tkinter's mainloop keeps running until the window is closed. Because of this mainloop, using a (long) while loop in tkinter is a bad idea, because the while loop locks up the tkinter mainloop.
To do something multiple times without blocking the mainloop, use the after method. This calls a function after a certain amount of time. You can call it once before entering your mainloop and then call it again in your update function, so that the update function calls itself. Take a look at this small example which uses after to execute an update function every second:
import Tkinter as tk
import random
def update():
l.config(text=str(random.random()))
root.after(1000, update)
root = tk.Tk()
l = tk.Label(text='0')
l.pack()
root.after(1000, update)
root.mainloop()

you could use the .update to the window. That would make it update the window and allow everything to be on it.

Related

Real Time Data with tkinter

I am looking for a simple way to display changing real time data in a GUI in python. I am connected to 2 devices and want to display data constantly (like 20 different values), and when I press a button I want to control the one device.
Unfortunately I fail already with the display of the data. For this I have looked at some tkinter tutorials and explanations.
My idea was to implement it with a config function and to overwrite the label continuously. As example how I wanted to display one value:
import tkinter as tk
from pydualsense import pydualsense
# connect to the device
dualsense = pydualsense()
dualsense.init()
# create a window
window = tk.Tk()
# function for updating data
def show_data():
global dualsense
data_label_output.config(text=dualsense.state.LX)
# showing the data as a lable
data_label_output = tk.Label(window)
data_label_output.grid(row=1, column=1)
show_data()
#### or different solution
# showing the data as a lable
data_label_output = tk.Label(window, comand=show_data)
data_label_output.grid(row=1, column=1)
window.mainloop()
Unfortunately, the value is displayed only once at the beginning and nothing changes after that.
Another problem:
When I press the button, I want to be able to control the one device. For this I have a while True loop that permanently checks if a button is pressed and then executes actions. As a separate program no problem, but how do I integrate this into the tkinter GUI? When I start this PyCharm always crashes.
I use PyCharm and Python 3.8
About simple and functional ideas I would be happy, also to other tools/modules etc., as long as you can easily and quickly implement the idea. It's only for a research project and the programming is only a means to an end.
You can use the after method in tkinter to run something after a short delay. The following code will run show_data once the GUI is ready and then again every 1000 milliseconds.
import tkinter as tk
from pydualsense import pydualsense
# connect to the device
dualsense = pydualsense()
dualsense.init()
# create a window
window = tk.Tk()
# function for updating data
def show_data():
global dualsense
data_label_output.config(text=dualsense.state.LX)
window.after(1000,show_data)
# showing the data as a lable
data_label_output = tk.Label(window)
data_label_output.grid(row=1, column=1)
window.after_idle(show_data)
window.mainloop()
This resolves the updating issue, I'm not sure what behaviour you want when you press the button but if you elaborate and explain, I might be able to help and update this answer.

Python tkinter update function freezes main function

I have a windows application with a python API (supports python 3). You can provide a class that has a main function. On program startup, an instance of this class is created, and the main function is called periodically. I can configure the frequency, but nothing else. (I cannot change the way the API works)
Now, I'd like to use tkinter for some visualizations, charts, and so on. Here is an example of my current class structure:
import tkinter as tk
class Script:
def __init__(self):
self.root = tk.Tk()
self.count = 0
def main(self):
# do some stuff
print(str(self.count))
self.count += 1
# update gui
self.root.update()
# This is to simulate the software behavior.
import time
scriptInstance = Script()
for i in range(0, 20 * 10):
scriptInstance.main()
time.sleep(0.1)
The main issue I have is, that the root.update() method freezes the main-function as long as you interact with Tkinter. You can see that with the example above, that the number is not counting up as long as you are moving the window. Is there a way to prevent that?
I tried to call the root.update() form another thread, which did not work because Tkinter does not allow that.
Thank you for any help.

Python: How do you obtain variables from another script which are constantly being updated

I've made a script that uses a while True loop to constantly update a series of variables based on UDP packets that I am constantly recieving. I want to ultimately create a GUI that displays that data and updates the screen constantly, which I plan to do with tkinter (using my_label.after in a function which then calls itself, not sure if this is a good plan).
Here is some testing scripts that I can't get to work properly:
GUI2.py (my test looping script)
import time
var = 0
while True:
var += 1
time.sleep(0.1)
GUI Testing.py (the script that would be accessing those variables)
from GUI2 import *
import time
print('never')
print(var)
time.sleep(1)
The second script never reaches the print('never') line, I think because it gets stuck in the other script's while True loop and never returns.
How should I go about this? I have one script that I want in a constant loop to update my variables to the correct values based on incoming packets, and then another script updating a tkinter window. I went this way as most examples I could find using Tkinter didn't use any sort of while True loops. Could I just put my packet recieving code inside the Tkinter mainloop, and would that effectively act as a while True?
EDIT (added Tkinter loop that I can't get working):
This opens a Tkinter window, but the label stays at 99, then reopens a window when I close it with the new x value (ie. 98, 97, etc). I want the label to update every second.
import tkinter as tk
import time
x = 99
while True:
root = tk.Tk()
label = tk.Label(root, text=x)
label.pack()
x -= 1
time.sleep(1)
root.mainloop()
Below is a sample script to show you how you can update the value in the label widget at a certain time interval. I have provided you the hyperlinks to help you understand tkinter's methods. Best regards.
Key points:
use the textvariable option of the tk.Label widget.
use tkinter's control variable. I have shown you how to set and get it's value.
you can use tkinter's widget method called .after() without having to explicitly use a while-statement and time.sleep() method. Tkinter has it's own event loop that you can use.
writing your tkinter GUI as a class makes it easier to implement what you need.
Example Script:
import tkinter as tk
class App(tk.Frame):
def __init__( self, master, *args, **kw ):
super().__init__( master )
self.master = master
self.create_label()
self.update_label()
def create_label( self ):
self.var = tk.IntVar() # Holds an int; default value 0
self.label = tk.Label(self, textvariable=self.var ) # Use textvariable not text
self.label.pack()
def update_label( self ):
value = self.get_value()
self.var.set( value ) # Set the label widget textvariable value.
self.after(1000, self.update_label) # Call this method after 1000 ms.
def get_value( self ):
'''To simulate calling a function to return a value'''
value = self.var.get() + 1
return value
if __name__ == "__main__":
root = tk.Tk()
root.geometry('100x100+0+24')
app = App( root )
app.pack()
root.mainloop() #This command activates tkinter's event loop
Edit:
As a clarification, this answer shows how to utilize the .after() and .mainloop() methods in GUI Testing.py, i.e. using tkinter event loop and not use two while-loops, to achieve what you wanted to do. This is a way to simplify your GUI script.
For more sophisticated algorithms, e.g. more than one while-loop is involved, you have to look into using threads(note it has its issues) or more recently I found a way of using python's Asyncio approach to do it. The learning curve for these two approaches is a lot steeper. To use the asyncio approach, you can explore modifying my answer to do what you want.
Best solution is to use threads however If you plan to do in simplest possible manner then implement the main loop inside your Tkinter GUI and once you read the packet simply update it on your GUI in same loop. Here is the Updated and working Code.
import tkinter as tk
import time
def setvalue(self, x):
self.label.config(text=x, )
root.update()
time.sleep(1)
def changevalues(self):
x = 99
self.label = tk.Label(root, text=x)
self.label.pack()
while x >0:
x -= 1
setvalue(root,x)
root = tk.Tk()
changevalues(root)
root.mainloop()

Python: Is it possible to create an tkinter label which has a dynamic string when a function is running in background?

I have created a tkinter GUI for my python script. When I run the script, I want a dynamic string in one of the Label widgets on the GUI window, which will display:
"Working."
Then:
"Working.."
then
"Working..."
and then start from "Working." again until the script is completed.
(Actually I'd prefer a progress bar in this area)
Is it possible?
I wrote two simple scripts to help demonstrate how to do what you want. The first is using the label:
import tkinter as tk
root = tk.Tk()
status = tk.Label(root, text="Working")
status.grid()
def update_status():
# Get the current message
current_status = status["text"]
# If the message is "Working...", start over with "Working"
if current_status.endswith("..."): current_status = "Working"
# If not, then just add a "." on the end
else: current_status += "."
# Update the message
status["text"] = current_status
# After 1 second, update the status
root.after(1000, update_status)
# Launch the status message after 1 millisecond (when the window is loaded)
root.after(1, update_status)
root.mainloop()
The next one is using a progressbar:
import tkinter as tk
# You will need the ttk module for this
from tkinter import ttk
def update_status(step):
# Step here is how much to increment the progressbar by.
# It is in relation to the progressbar's length.
# Since I made the length 100 and I am increasing by 10 each time,
# there will be 10 times it increases before it restarts
progress.step(step)
# You can call 'update_status' whenever you want in your script
# to increase the progressbar by whatever amount you want.
root.after(1000, lambda: update_status(10))
root = tk.Tk()
progress = ttk.Progressbar(root, length=100)
progress.pack()
progress.after(1, lambda: update_status(10))
root.mainloop()
Note however that I couldn't do too much with the progressbar script because progressbars are a little tricky and need to be customized to your script exactly. I just wrote it to maybe shed a little light on the subject. The main part of my answer though is the label script.
Yes, it is possible. There are two ways to do it:
Whenever you want to update the label from your code you can call the_widget.configure(the_text). This will change the text of the label.
You can create an instance of a tkinter.StringVar, and assign it to the textvariable attribute of a label. Whenever you change the value of the variable (via the_variable.set(the_text), the label will automatically update.
Note that for either of these to work, the event loop needs to be able to process events (ie: you won't see anything if your function takes a long time to run and you never call update_idletasks or re-enter the event loop).

Tkinter in Python - Remove widget from active window

I'm trying to remove a Tkinter progress bar widget from an active window (after the GUI window using Tkinter has been initialized). I'm using a Tkinter Frame for my window. I've initialized the progress bar as pb, as below.
pb = ttk.Progressbar(root,orient ="horizontal",length = 540, mode ="determinate")
And then I've tried two different methods to get rid of the progress bar. The line below causes the window to freeze and stop responding when I try to use it after the GUI is initialized.
pb.pack_forget()
The line below causes only a middle section of the progress bar to disappear, but you can still see the two sides of it.
pb.destroy()
Is there any way I could get this widget to disappear after the Frame has been initialized?
The specific answer to your question is that pack_forget, grid_forget or grid_remove are what you want if you want to make a widget temporarily invisible. Which one you choose depends on if you're using grid or pack, and whether or not you want grid to remember where it was so you can later put it back in the same spot.
destroy is what you want to call if you want to literally destroy the widget.
When used properly, none of those methods will cause your program to freeze. Without seeing your code, it's impossible to know what the root cause of the problem is.
Sorry for my bad English.
This code worked for me. I simply follow Oakley's instruction.
def progressBar(*args, **kwargs):
def progress(currentValue):
progressbar["value"] = currentValue
maxValue = 100
progressbar = ttk.Progressbar((kwargs), orient="horizontal", length=150, mode="determinate", takefocus=True)
progressbar.pack(side=tk.BOTTOM)
currentValue = 0
progressbar["value"] = currentValue
progressbar["maximum"] = maxValue
divisions = 10
for i in range(divisions):
currentValue = currentValue + 10
progressbar.after(500, progress(currentValue))
progressbar.update() # Force an update of the GUI
progressbar.destroy()
Hence I simply tried progressbar.destroy() outside of the loader loop. So after
complete the loading, it will disappear from the main App window.
Thank you, Bryan Oakley sir.

Categories

Resources