I have a simple tool which is crashing every few days. I simplified it to show the problem.
Short description:
A counter is incremeted and displayed with Tk as a Label.
Problem:
There must be a memory leak - with this demo script I can crash a 1 GB Raspberry in 5 - 10 minutes...
The problem is the label-update (labeltext.set(counter))
Without updating the label it works for hours without a problem.
But why? I really don't understand it...
Thank you.
from tkinter import *
root = Tk()
counter=1
labeltext = StringVar()
root.config(cursor="none")
root.configure(background='black')
root.geometry("1920x1080") # set explicitly window size
frame_0= Frame(root, padx=5, pady=5, background='black' )
label1 = Label(frame_0, font=('LCD Display Grid', 170, ''), fg='lightgreen',bg='black', pady=30, textvariable=labeltext)
frame_0.grid( row=0, columnspan=3,pady=10, padx=0)
label1.pack()
root.grid_columnconfigure(0, weight=1)
class perpetualTimer():
def __init__(self, t, hFunction):
self.t = t
self.hFunction = hFunction
self.thread = Timer(self.t, self.handle_function)
def handle_function(self):
self.hFunction()
self.thread = Timer(self.t, self.handle_function)
self.thread.start()
def start(self):
self.thread.start()
def cancel(self):
self.thread.cancel()
def printer():
global counter
counter = counter + 1
labeltext.set(counter)
t = perpetualTimer(0.001, printer)
t.start()
root.mainloop()
Related
When I run the app, the run button is still responsive for a few seconds before the entire app freezes but the thread continues running. Where is the problem in my code and how do fix it?
import threading
from tkinter import *
from tkinter import ttk
class SubExtractor:
def __init__(self, root):
self.root = root
self._create_layout()
def _create_layout(self):
self.root.title("Sub Extractor")
self._menu_bar()
self.main_frame = ttk.Frame(self.root, padding=(5, 5, 5, 15))
self._video_frame()
self._progress_frame()
self._output_frame()
self.main_frame.grid()
def _menu_bar(self):
self.root.option_add('*tearOff', FALSE)
menubar = Menu(self.root)
self.root.config(menu=menubar)
menu_file = Menu(menubar)
menu_settings = Menu(menubar)
menubar.add_cascade(menu=menu_file, label="File")
menubar.add_cascade(menu=menu_settings, label="Settings")
menu_file.add_command(label="Open", command=self._open_file)
menu_file.add_command(label="Close", command=self._close_window)
menu_settings.add_command(label="Language", command=self._language_settings)
menu_settings.add_command(label="Extraction", command=self._extraction_settings)
def _video_frame(self):
video_frame = ttk.Frame(self.main_frame, borderwidth=2, relief="ridge", width=1000, height=600)
video_frame.grid()
def _progress_frame(self):
progress_frame = ttk.Frame(self.main_frame)
progress_frame.grid(row=1, sticky="W")
self.run_button = ttk.Button(progress_frame, text="Run", command=self._run)
self.run_button.grid(pady=10, padx=30)
self.progress_bar = ttk.Progressbar(progress_frame, orient=HORIZONTAL, length=800, mode='determinate')
self.progress_bar.grid(column=2, row=0)
def _output_frame(self):
output_frame = ttk.Frame(self.main_frame)
output_frame.grid(row=2)
self.text_output_widget = Text(output_frame, width=97, height=12, state="disabled")
self.text_output_widget.grid()
output_scroll = ttk.Scrollbar(output_frame, orient=VERTICAL, command=self.text_output_widget.yview)
output_scroll.grid(column=1, row=0, sticky="N,S")
self.text_output_widget.configure(yscrollcommand=output_scroll.set)
def _close_window(self):
self._stop_run()
self.root.quit()
def _stop_run(self):
self.interrupt = True
self.run_button.configure(text="Run", command=self._run)
def _text_to_output(self, text):
self.text_output_widget.configure(state="normal")
self.text_output_widget.insert("end", f"{text}\n")
self.text_output_widget.see("end")
self.text_output_widget.configure(state="disabled")
def long_running_method(self):
num = 100000
self.progress_bar.configure(maximum=num)
for i in range(0, num):
if self.interrupt:
break
self._text_to_output(f"Line {i} of {num}")
self.progress_bar['value'] += 1
self._stop_run()
def _run(self):
self.interrupt = False
self.run_button.configure(text='Stop', command=self._stop_run)
threading.Thread(target=self.long_running_method).start()
rt = Tk()
SubtitleExtractorGUI(rt)
rt.mainloop()
What I expect to accomplish is for the loop to insert texts in the text widget while I can still freely move and use other buttons on the app.
I need a tkinter button to open only one Toplevel
but I tried using a counter that changes to 2 when one Toplevel is made and a if loop so it checks if the counter is 1, (it will make a Toplevel window if its 1)
but when I run the program I can make many windows by clicking the button multiple times
I think the solution of using a counter doesn't work
def menu_window(self): # this is in a class
self.frame4 = tk.Frame(self.master, padx=30, pady=30)
self.frame4.grid()
Counter = 1 ### COUNTER
button2 = tk.Button(self.frame4, text="Review your Quiz", command=lambda: PreviewQuiz(self.master, self.frame4,
Counter))
button2.grid(row=3, column=2, padx=40, pady=15, ipadx=28)
button3 = tk.Button(self.frame4, text=" Start your Quiz ")
button3.grid(row=4, column=2, padx=40, pady=5, ipadx=30)
class PreviewQuiz:
def __init__(self, master, frame4, Counter):
if Counter == 1: # CHECK IF COUNTER IS 1
self.master = master
self.review_q = tk.Toplevel(self.master)
self.frame5 = tk.Frame(self.master, padx=50, pady=20)
self.frame5.grid()
self.Counter = 2 # SET COUNTER TO 2
Just disable the button when the user clicks on it and enable it only when the top-level is closed.
Here is an example taken from the code you provided in your newest post:
import tkinter as tk
class Run:
def __init__(self, master):
self.master = master
self.button = tk.Button(master, text="TopLevel", command=self.make_new)
self.button.pack()
def make_new(self):
self.button['state'] = 'disabled'
new = tk.Toplevel(self.master)
lbl = tk.Label(new, text='only one topLevel')
lbl.pack()
new.protocol("WM_DELETE_WINDOW", lambda : self.button.configure(state='normal') or new.destroy()) # or make a method to change the state
master1 = tk.Tk()
i = Run(master1)
master1.mainloop()
Your code does not run, which makes it difficult to debug. There are however a few things:
When you assingn a value to counter in menu_window() you end the value with a ".", which makes it a float, not an integer.
You call PreviewQuiz with the counter argument, but when you update the counter you do it to an instance variable of PreviewQuiz, not of menu_window() class so next time you call PreviewQuiz you will still use 1.0.
I'm trying to create code to incrementally increase the voltage on a DC power supply over the span of an input duration. I've set up a GUI for doing this (it's my first try making a GUI, sorry if the code is weird), and everything works ... except that the GUI freezes while the code is executing so I can't stop the loop. I've looked into this for several hours and learned to use root.after instead of time.sleep, but it doesn't seem to have helped in the HeatLoop function. The GUI updates now, but only sporadically and there's still the "wait cursor" showing up when I mouse over the GUI. Is there some way to fix this?
I modified the code I'm using below so it should work on any computer without needing to be edited.
import datetime
import time
from tkinter import *
class GUIClass:
def __init__(self, root):
"""Initialize the GUI"""
self.root = root
self.percent = StringVar()
self.percent.set("00.00 %")
self.error = StringVar()
self.STOP = False
self.error.set("---")
self.currentvoltage = StringVar()
self.currentvoltage.set("Current Voltage: 00.00 V")
self.DT = datetime.datetime
# Create and attach labels
label1 = Label(root, text='Voltage')
label2 = Label(root, text='Ramp Duration')
label3 = Label(root, text='Percent Done: ')
label4 = Label(root, textvariable=self.percent)
label5 = Label(root, text="Error Message: ")
label6 = Label(root, textvariable=self.error)
label7 = Label(root, textvariable=self.currentvoltage)
label1.grid(row=0, column=0, sticky=W)
label2.grid(row=1, column=0, sticky=W)
label3.grid(row=2, column=0, sticky=W)
label4.grid(row=2, column=1, sticky=W)
label5.grid(row=3, column=0, sticky=W)
label6.grid(row=3, column=1, sticky=W)
label7.grid(row=3, column=2, sticky=E)
# Create and attach entries
self.voltage = Entry(root)
self.duration = Entry(root)
self.voltage.grid(row=0, column=1)
self.duration.grid(row=1, column=1)
# Create, bind, and attach buttons
HeatButton = Button(root, text='Heat')
HeatButton.bind("<Button-1>", self.Heat)
HeatButton.grid(row=0, column=2)
CoolButton = Button(root, text='Cool')
CoolButton.bind("<Button-1>", self.Heat)
CoolButton.grid(row=1, column=2)
StopButton = Button(root, text='Stop')
StopButton.bind("<Button-1>", self.Stop)
StopButton.grid(row=2, column=2)
def HeatLoop(self, condition, TimeStart, TimeDuration, MaximumVoltage, Fraction=0):
"""Heat up the cell while the condition is true"""
if condition:
self.percent.set("{:2.2f}%".format(Fraction * 100))
print(MaximumVoltage)
self.currentvoltage.set("Current Voltage: {:2.2f} V".format(Fraction*MaximumVoltage))
self.Update()
CurrentTime = self.DT.now()
ElapsedTime = (CurrentTime.second/3600 + CurrentTime.minute/60 + CurrentTime.hour
- TimeStart.second/3600 - TimeStart.minute/60 - TimeStart.hour)
Fraction = ElapsedTime / TimeDuration
print(Fraction)
self.root.after(5000)
self.HeatLoop(bool(not self.STOP and Fraction < 1),
TimeStart, TimeDuration, MaximumVoltage, Fraction)
# Define function to heat up cell
def Heat(self, event):
# Initialize Parameters
self.STOP = False
self.error.set("---")
self.Update()
# Try to get voltage and duration from the GUI
MaxVoltage = self.voltage.get()
TimeDuration = self.duration.get()
try:
MaxVoltage = float(MaxVoltage)
try:
TimeDuration = float(TimeDuration)
except:
self.error.set("Please enter a valid time duration")
self.Update()
self.STOP = True
except:
self.error.set("Please enter a valid voltage value")
self.Update()
self.STOP = True
TimeStart = self.DT.now()
self.HeatLoop(True,
TimeStart, TimeDuration, MaxVoltage)
def Stop(self, event):
self.STOP = True
print("turned off voltage")
def Update(self):
self.root.update_idletasks()
self.root.update()
root1 = Tk()
a = GUIClass(root1)
root1.mainloop()
root.after(5000) is no different than time.sleep(5). It's doing exactly what you're telling it to: to freeze for five seconds.
If you want to run self.HeatLoop every five seconds, the way to do it is like this:
self.root.after(5000, self.HeatLoop,
bool(not self.STOP and Fraction < 1),
TimeStart, TimeDuration, MaximumVoltage,
Fraction)
When you give two or more arguments to after, tkinter will add that function to a queue, and will call that function after the time has expired. This allows the event loop to continue to process events during the five second interval.
A slightly better way to write it would be to check for the condition inside the function rather than passing the condition in, so that the condition is evaluated immediately before doing the work rather than five seconds before doing the work.
For example:
def HeatLoop(self, TimeStart, TimeDuration, MaximumVoltage, Fraction=0):
if self.STOP and Fraction < 0:
return
...
self.root.after(5000, self.HeatLoop,
TimeStart, TimeDuration, MaximumVoltage,
Fraction)
I am new to python and tkinter and I have decided that I will make a stopwatch.
I have gooled alot and find many useful information, but I still haven't found how to display value of a function in tkinter. Here is my current code:
import time
from tkinter import*
import os
root = Tk()
def clock(event):
second = 0
minute = 0
hour = 0
while True:
time.sleep(0.99)
second +=1
print(hour,":",minute,":",second)
return
def stop(event):
time.sleep(1500)
def clear(event):
os.system('cls')
button1 = Button(root, text="Start")
button2 = Button(root, text="Stop")
button3 = Button(root, text="Clear")
button1.bind("<Button-1>", clock)
button2.bind("<Button-1>", stop)
button3.bind("<Button-1>", clear)
button1.grid(row=2, column=0, columnspan=2)
button2.grid(row=2, column=2, columnspan=2)
button3.grid(row=2, column=4, columnspan=2)
root.mainloop()
I am aware that the code isn't perefect yet(especially the functions stop and clear).
You might consider using callback functions (i.e. call to your function when something happens — when clicking a button for example):
Quoting portions of Tkinter Callbacks:
In Tkinter, a callback is Python code that is called by Tk when
something happens. For example, the Button widget provides a command
callback which is called when the user clicks the button. You also use
callbacks with event bindings.
You can use any callable Python object as a callback. This includes
ordinary functions, bound methods, lambda expressions, and callable
objects. This document discusses each of these alternatives briefly.
...
To use a function object as a callback, pass it directly to Tkinter.
from Tkinter import *
def callback():
print "clicked!"
b = Button(text="click me", command=callback)
b.pack()
mainloop()
It's unclear from your sample code which function's value you want to display.
Regardless, a good way to do accomplish something like that in tkinter is by creating instances of its StringVar Variable class and then specifying them as the textvariable option of another widget. After this is done, any changes to the value of the StringVar instance will automatically update the associated widget's text.
The code below illustrates this:
import os
import time
import tkinter as tk
class TimerApp(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master=None)
self.grid()
self.create_widgets()
self.elapsed = 0
self.refresh_timer()
self.after_id = None # used to determine and control if timer is running
def create_widgets(self):
self.timer = tk.StringVar()
self.timer.set('')
self.timer_label = tk.Label(self, textvariable=self.timer)
self.timer_label.grid(row=1, column=2)
self.button1 = tk.Button(self, text="Start", command=self.start_clock)
self.button1.grid(row=2, column=0, columnspan=2)
self.button2 = tk.Button(self, text="Stop", command=self.stop_clock)
self.button2.grid(row=2, column=2, columnspan=2)
self.button3 = tk.Button(self, text="Clear", command=self.clear_clock)
self.button3.grid(row=2, column=4, columnspan=2)
def start_clock(self):
self.start_time = time.time()
self.after_id = self.after(1000, self.update_clock)
def stop_clock(self):
if self.after_id:
self.after_cancel(self.after_id)
self.after_id = None
def clear_clock(self):
was_running = True if self.after_id else False
self.stop_clock()
self.elapsed = 0
self.refresh_timer()
if was_running:
self.start_clock()
def update_clock(self):
if self.after_id:
now = time.time()
delta_time = round(now - self.start_time)
self.start_time = now
self.elapsed += delta_time
self.refresh_timer()
self.after_id = self.after(1000, self.update_clock) # keep updating
def refresh_timer(self):
hours, remainder = divmod(self.elapsed, 3600)
minutes, seconds = divmod(remainder, 60)
self.timer.set('{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds))
app = TimerApp()
app.master.title('Timer')
app.mainloop()
I am trying to populate my screen with some data that refreshes every given time interval. I am using Python3, themed tkinter. Every time my screen updates I see grey flickerings on screen for every label. Is there a way to avoid this?
P.S : I am calling the 'after' method to refresh data.
UPDATE: Here is some example code:
def button1Click(self):
self.top = Toplevel(width=600,height=400)
self.top.title("XYZ ORGANIZATION")
self.frame1 = Frame(self.top,bg='#009999')
self.frame1.pack()
self.noOfEmp = Label(self.frame1,text = "Number_Of_Employees : ", font =('Verdana',9, 'bold'),bg='#009999',fg = '#000000')
self.noOfEmp.grid(row=1,column=0,sticky=W,padx=0,pady=5)
self.TeamLabel = Label(self.frame1,text = "Team Name : ", font =('Verdana',9, 'bold'),bg='#009999',fg = '#000000')
self.TeamLabel.grid(row=2,column=0,sticky=W,padx=0,pady=5)
self.text = Text(self.frame1, bg='#009999')
self.text.grid(row=8,columnspan=17)
self.old_emp = 0
self.EFile = open('/abc','r').readlines()
for line in self.EFile:
if line.startswith('EmpTotal:'):
self.Tot_Emp = int(line.split()[1])
break
t1 = threading.Thread(target=self.__make_layout, args = ())
t1.daemon = True
t1.start()
t2 = threading.Thread(target=self.ProcEmp,args = ())
t2.daemon = True
t2.start()
def self.__make_layout:
self.CLabelVal = Label(self.frame1,text = CSpace, font=('Verdana',9),bg='#009999',fg = '#000000')
self.MLabelVal = Label(self.frame1,text = MSpace , font =('Verdana',9),bg='#009999',fg = '#000000')
self.Label1Val.grid(row=4,column=1,sticky=W+E+N+S,padx=5,pady=5)
self.Label2Val.grid(row=5,column=1,sticky=W+E+N+S,padx=5,pady=5)
self.frame1.after(5000,self.__make_layout)
Part of the problem is that you keep stacking more and more widgets on top of each other. You should create the labels exactly once, and then change what they display every five seconds, instead of creating new widgets every five seconds.
There's also the problem that you're creating the labels in a thread. Tkinter isn't thread safe. Any code that creates or modifies a widget needs to be in the main thread. To update the labels you don't need threads, though you can use a thread to change what actually gets displayed.
def __make_layout(self):
self.CLabelVal = Label(...,text = CSpace, ...)
self.MLabelVal = Label(...,text = MSpace, ...)
self.Label1Val.grid(...)
self.Label2Val.grid(...)
def __update_layout(self):
self.CLabelVal.configure(text=CSpace)
self.MLabelVal.configure(text=MSpace)
self.after(5000, self.__update_layout)
I made a small program based on what you provided.
This is what i got. I chose 500ms instead because i did not want to wait that long. I ran two internet videos at the same time and had no issues.
So my best guess you have a slow video card or over loaded computer.
from tkinter import *
class MyClass:
frame1 = Tk()
poll = 0
def __init__(self):
self.frame1.after(500, self.__make_layout)
def __make_layout(self):
self.poll += 1
CSpace = "Poll count = "*20
MSpace = str(self.poll)
self.CLabelVal = Label(self.frame1, text=CSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.MLabelVal = Label(self.frame1, text=MSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.CLabelVal.grid(row=4, column=1, sticky=W+E+N+S, padx=5, pady=5)
self.MLabelVal.grid(row=5, column=1, sticky=W+E+N+S, padx=5, pady=5)
print(CSpace, MSpace)
return self.frame1.after(500, self.__make_layout)
MyClass()
mainloop()
This doesn't create more labels and uses the "textvariable" update feature.
from tkinter import *
class MyClass:
frame1 = Tk()
poll = 0
textstg = StringVar()
CSpace = "Poll count"
def __init__(self):
self.frame1.after(500, self.__make_layout)
self.CLabelVal = Label(self.frame1, text=self.CSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.MLabelVal = Label(self.frame1, textvariable=self.textstg, font=('Verdana', 9), bg='#009999', fg='#000000')
self.CLabelVal.grid(row=4, column=1, sticky=W+E+N+S, padx=5, pady=5)
self.MLabelVal.grid(row=5, column=1, sticky=W+E+N+S, padx=5, pady=5)
def __make_layout(self):
self.poll += 1
self.textstg.set(str(self.poll))
return self.frame1.after(50, self.__make_layout)
MyClass()
mainloop()