I'm trying to use Tkinter in order to create a photon counter, but here I will use the time.time()
to simulate the data constantly changing. I have tried to synthesize my code to find and easily reproduce the problem.
The goal is to have a class object from which a method creates a panel that shows the actual time as fast as possible. With the knowledge that later on I will close the panel to do other processes, so my code must continue to run after I close the window.
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
import tkinter as tk
import time
class GUI:
def __init__(self, master : tk.Tk, string : str):
self.master = master
self.string = time.time()
master.title('A simple window')
self.label = tk.Label(master, text=self.string)
self.label.pack()
def update_text(self, string):
self.string = time.time()
self.label.config(text=self.string)
root=tk.Tk()
my_gui = GUI(root,"HOLA")
while 1:
my_gui.update_text(time.time())
my_gui.master.update_idletasks()
my_gui.master.update()
root.mainloop()
The code works very well in my opinion. But when I close the panel I get the error TclError: invalid command name ".!label"
.
I then tried another version with VarStr
and I included my time variable at the beginning of my class (sorry if the description is unclear, but you can look at the code and it will be easier to understand):
class GUI:
def __init__(self, master : tk.Tk, value):
self.master = master
self.string=tk.StringVar()
self.string.set(value) #The textvariable already updates the label automatically when the StringVar changes.
master.title("A simple window")
self.label = tk.Label(master, textvariable=self.string)
self.label.pack()
self.label.after(1, self.update_text)
def update_text(self):
self.string.set(self.string)
self.label.after(1, self.update_text)
root=tk.Tk()
my_gui = GUI(root,time.time())
root.mainloop()
Now I don't get any Error in the log, but the panel no longer updates and only shows "PY_VAR". I also no longer have my infinite loop from which I get an updated time.time(), which was useful as in my real code I have loops upon loops in which in one of them my variable of interest is updated and when I finished with it I could just close the panel and then should be able to continue running my code.
Solution found! Here is the updated code.
import tkinter as tk
import time
class GUI:
def __init__(self, Main_Window):
# Initialize
self.isRunning = True
self.m = Main_Window
self.PhotonVar = tk.StringVar()
self.PhotonVar.set("Starting...")
self.m.title("A simple window")
tk.Button(self.m,text = "Quit", command = self.stop).pack()
self.label = tk.Label(self.m, textvariable = self.PhotonVar )
self.label.pack()
#self.label.after(1,self.update_text(self.PhotonVar.get())) #Increasing this time value for slower processes doesn't work. The wndow will just open later on.
self.m.protocol("WM_DELETE_WINDOW", self.stop)
def update_text(self, new_value):
self.PhotonVar.set(new_value)
#time.sleep(1)
def stop(self):
self.isRunning = False
self.m.destroy()
root = tk.Tk()
my_gui = GUI(root)
while my_gui.isRunning:
my_gui.update_text(time.time())
my_gui.m.update_idletasks()
my_gui.m.update()
Things to know :
The order of "reading" will be : root
-> my_gui
(inside my_gui
you do each step until label.after
where you run once the method update_text
then you go back init
and continue where you left off until the end of the init
) -> loop while isRunning:
-> update_text(NEW VALUE)
which loops infinitly unless you click on Quit or Close button.
Don't write self.stop()
in the command or the program will run it as it creates the Button, thus closing the window.
Use string.set()
to change the value of the StringVar
, then use string.get()
whenever you need the value. But don't forget that when introducing your textvariable
you plainly use string
. It's not confusing at all right?
I understqnd that sometimes time.sleep()
doesn't work well with tkinter and it better to use .after
on the Label,
but I don't know where to actually input this function without breaking my code.
With the functions update_idletask()
, update()
and the method stop()
I am basically replicating a mainloop()
, so no need to write one at the end of the code.
Hope this helps someone in the future!