RuntimeError when using threading and tkinter - python

I am creating a program that runs multiple threads where each thread updates a variable and then displays that value using tkinter.
The only problem is that I get a RuntimeError whenever I try and update the display:
Exception in thread Thread-x:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "program.py", line 15, in body
update()
File "program.py", line 11, in update
display.config({"text" : "x = {0}".format(x)})
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 1479, in configure
return self._configure('configure', cnf, kw)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 1470, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
RuntimeError: main thread is not in main loop
Some of the solutions I have tried to fix the error are:
Make the display object global to the function (using global)
Create a seperate function to update the display
However, none of these solutions worked (the RuntimeError still kept occurring).
Below is my program:
import tkinter, time, threading
window = tkinter.Tk()
x = 0
display = tkinter.Label(window)
display.pack()
def update():
global x
x += 1
display.config({"text" : "x = {0}".format(x)}) #It says the error is on this line
def body():
time.sleep(3)
update()
body()
def start_threads():
for i in range(5):
thread = threading.Thread(target=body)
thread.start(); thread.join()
start = tkinter.Button(window, text="Start", command=start_threads)
start.pack()
I do not know how to fix the RuntimeError, so any help with that would be appreciated.

This is actually due to your sleep function, this freezes the main thread for tkinter which you cannot do.
Here's some code that will work:
import tkinter
x = 0
repeat = 0
def start_counter():
global x, repeat
repeat+=1
x += 1
display.config({"text" : "x = {0}".format(x)})
if repeat < 5:
#3000 because 1000 in a second
window.after(3000, start_counter)
window = tkinter.Tk()
display = tkinter.Label(window)
display.pack()
start = tkinter.Button(window, text="Start", command=start_counter)
start.pack()
window.mainloop()
Notice how I use "window.after(3000, function)". This tells tkinter to do something after 3 seconds and thus doesn't freeze the main thread. If you want it to sleep before even showing the number 1, you need to change a few things, in which case I'd be happy to update my code for you :)

After a bit of experimentation and the idea suggested by Piero Bird, I have come up this solution:
import tkinter, threading
def start_counter():
for i in range(1):
bot = threading.Thread(target=add_one)
bot.start(); bot.join()
temp_window = tkinter.Tk()
temp_window.withdraw()
window.after(100, start_counter)
def add_one():
global count
count += 1
if __name__ == "__main__":
temp = 0
count = 0
window = tkinter.Tk()
window.minsize(width=500, height=500)
display = tkinter.Label(window)
display.pack()
start = tkinter.Button(window, text="Start", command=start_counter)
start.pack()
def update():
global temp, first
if count != temp:
display.config({"text":"x = {0}".format(count)})
temp = count
window.after(1, update)
window.after(1, update)

Related

Tkinter: error main thread not in mainloop

this question has been asked many times but to be honest I don't quite understand why do I get this error "RuntimeError: main thread is not in main loop". I have the following code to read data from Serial and draw some graph with it. My problem is that my program is working if I do not try to draw in real time the data (so if I only keep take_measure() inside the plotter function). But if I add the part for the graph drawing as shown bellow my code is returning this mainloop error. What is happening ? I know I should use the after command but for me after command was a total disaster (a lots of bugs). So do you know what is the cause of my error and how to solve it/how to implement the after method ?
Sorry for my nasty code....
from tkinter import *
from random import randint
import numpy as np
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import time
import threading
import serial
from tkinter.filedialog import asksaveasfile
import csv
from skimage.restoration import (denoise_tv_chambolle, denoise_bilateral, denoise_wavelet, estimate_sigma)
continuePlotting = False
def change_state():
global continuePlotting
global serial_port
global i
global b_start
global mid
global label
if continuePlotting == True:
continuePlotting = False
serial_port.close()
i = 2
label.pack_forget()
else:
continuePlotting = True
label.pack(in_=mid)
t, p1 = [], []
i = 0
def take_measure():
global t
global p1
global t_temp
global p1_temp
global i
global serial_port
if i == 0:
serial_port =serial.Serial('COM5', 2000000)
serial_port.setDTR(False)
time.sleep(0.1)
serial_port.setDTR(True)
serial_port.flushInput()
p1_temp = []
t_temp = []
i = 1
elif i == 2:
serial_port =serial.Serial('COM5', 2000000)
serial_port.setDTR(False)
time.sleep(0.1)
serial_port.setDTR(True)
serial_port.flushInput()
p1_temp = []
t_temp = []
i = 1
try:
temp=serial_port.readline()[:-2].decode()
a = temp.index(";")
t_val = float(temp[:a])
p1_val = (float(temp[a+1:])-2640)*20/3520
t.append(t_val)
p1.append(p1_val)
t_temp.append(t_val)
p1_temp.append(p1_val)
except:
pass
def app():
global t_temp
global p1_temp
global i
global serial_port
global t
global p1
global b_start
global mid
global label
root = Tk()
root.config(background='white')
root.geometry("1000x700")
top = Frame(root)
bottom = Frame(root)
mid = Frame(root)
top.pack(side="top")
mid.pack(side="top")
bottom.pack(side="bottom", fill="both", expand=True)
fig = Figure()
ax = fig.add_subplot(111)
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.grid()
graph = FigureCanvasTkAgg(fig, master=root)
graph.get_tk_widget().pack(in_=bottom, side="top",fill='both',expand=True)
graph.draw()
toolbar = NavigationToolbar2Tk(graph, root, pack_toolbar=False)
toolbar.update()
def plotter():
while continuePlotting:
take_measure()
ax.clear()
ax.plot(t_temp, p1_temp)
graph.draw()
def gui_handler():
change_state()
t=threading.Thread(target=plotter, daemon=True)
t.start()
def Save():
files = [('CSV', '*.csv')]
file_name = asksaveasfile(filetypes = files, defaultextension = files)
with open(str(file_name.name),"w", newline='') as file:
Writer=csv.writer(file)
Writer.writerow(["temps en ms", "pression en V"])
for elt in range(len(t)):
Writer.writerow([t[elt], p1[elt]])
file.close()
def Clear():
global t
global p1
serial_port.close()
i = 0
ax.clear()
graph.draw()
t = []
p1 = []
def Draw():
l_t = [t[0]]
l_p1 = [p1[0]]
ax.cla()
ax.grid()
for elt in range(1,len(t)):
if t[elt] == 0:
l_p = denoise_wavelet(np.array(l_p1), method="VisuShrink", mode="hard", wavelet_levels=3, wavelet='haar', rescale_sigma='True')
print("max", max(l_p1))
print("min", min(l_p1))
ax.plot(l_t, l_p)
l_t, l_p1 = [], []
l_t.append(t[elt])
l_p1.append(p1[elt])
l_p = denoise_wavelet(np.array(l_p1), method="VisuShrink", mode="hard", wavelet_levels=3, wavelet='haar', rescale_sigma='True')
ax.plot(l_t, l_p)
graph.draw()
print("max", max(l_p1))
print("min", min(l_p1))
b_start = Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white")
b_start.pack(in_=top, side = LEFT)
button_quit = Button(master=root, text="Quit", command=root.destroy)
button_save = Button(root,text="Save", command=Save)
button_clear = Button(root,text="Clear graph", command=Clear)
button_draw = Button(root,text="Show graphs", command=Draw)
button_draw.pack(in_=top, side = LEFT)
button_clear.pack(in_=top, side = LEFT)
button_save.pack(in_=top, side = LEFT)
button_quit.pack(in_=top, side=LEFT)
label = Label(root, text = "WAIT")
toolbar.pack(in_=bottom, fill=X)
root.mainloop()
if __name__ == '__main__':
app()
The error is the following:
Exception ignored in: <function Variable.__del__ at 0x000001CCB0F3E3E0>
Traceback (most recent call last):
File "c:\Users\cbroggi\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 410, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: main thread is not in main loop
Exception ignored in: <function Image.__del__ at 0x000001CCB1098D60>
Traceback (most recent call last):
File "c:\Users\cbroggi\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 4083, in __del__
self.tk.call('image', 'delete', self.name)
RuntimeError: main thread is not in main loop
Exception ignored in: <function Variable.__del__ at 0x000001CCB0F3E3E0>
Traceback (most recent call last):
File "c:\Users\cbroggi\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 410, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: main thread is not in main loop
Exception ignored in: <function Variable.__del__ at 0x000001CCB0F3E3E0>
Traceback (most recent call last):
File "c:\Users\cbroggi\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 410, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: main thread is not in main loop
Since you cannot update the UI from a thread your graph.draw() method will cause this type of error.
As a solution try using the .after() method:
def update():
global graph
graph.draw()
root.after(1000, update) ##after 1000ms run update()
##WARNING: this will require some additoinal code to stop when you exit the code.
def plotter():
while continuePlotting:
take_measure()
ax.clear()
ax.plot(t_temp, p1_temp)
#graph.draw() #removed as its in a thread
update() ##triggers the update loop that re-draws the graph every 1000ms
root.mainloop()

What is this bizarre tkinter error in my timer?

I decided to make a tkinter timer but am receiving a very strange error.
Here is the code and the error:
import tkinter as tk
import time
window = tk.Tk()
window.title("Hello wold")
window.geometry("300x300")
timer = int(input("time in seconds"))
for i in range(timer):
timer -= 1
print(timer)
time.sleep(1)
hello = tk.Label("timer")
hello.pack()
tk.mainloop()
The error:
Traceback (most recent call last):
File "main.py", line 14, in <module>
hello = tk.Label("timer")
File "/usr/lib/python3.8/tkinter/__init__.py", line 3143, in __init__
Widget.__init__(self, master, 'label', cnf, kw)
File "/usr/lib/python3.8/tkinter/__init__.py", line 2561, in __init__
BaseWidget._setup(self, master, cnf)
File "/usr/lib/python3.8/tkinter/__init__.py", line 2530, in _setup
self.tk = master.tk
AttributeError: 'str' object has no attribute 'tk'
To change the text of a label you need to define the text property:
hello = tk.Label(text="timer")
This will set the text of the label to "timer", but if you want to set it as the variable you previously declared (called timer) - simply remove the quotation marks.
hello = tk.Label(text=timer)
This will set the text of the label to whatever the variable 'timer' is equal to.
However you also need to set the window, which leaves the final code to this:
hello = tk.Label(window, text=timer)
I added tk.StringVar in line 16.
I added textvariable=string_variable in line 17
tk.mainloop was relocated outside of the loop block in line 20
Here is code:
import tkinter as tk
import time
window = tk.Tk()
window.title("Hello wold")
window.geometry("300x300")
timer = int(input("time in seconds"))
for i in range(timer):
timer -= 1
print(timer)
time.sleep(1)
string_variable = tk.StringVar(window, timer)
hello = tk.Label(window, textvariable=string_variable)
hello.pack()
tk.mainloop()
Output result:

How to automaticlly close or hide a tkinter window and be able to make it appear on conditions?

I'm trying to make a more user friendly screen crosshair for games like Apex Legends,because exsisted ones can't atuomaticlly go disappear when you returns to desktop,minimize the game window or switch to a browers while the game is still running with borderless window mode.I chose Python to make it because I'm most familiar with it(just started to learn coding recently).You can see my codes and error information below:
import time
from tkinter import *
class CrosshairWindow:
def __init__(self):
self.root = Toplevel()
self.root.attributes(
'-transparentcolor', '#ffffff',
'-topmost', '1'
)
self.root.overrideredirect(1)
self.screen_width = self.root.winfo_screenwidth()
self.screen_height = self.root.winfo_screenheight()
def start(self):
image = PhotoImage(file='crosshair.png')
canvas = Canvas(self.root, width=image.width(), height=image.height(), highlightthickness=0)
canvas.pack(fill='both')
canvas.create_image(0, 0, anchor=NW, image=image)
self.root.geometry(f'%sx%s+%s+%s' % (
image.width(), # width
image.height(), # height
round(self.screen_width/2 - image.width()/2), # x offset
round(self.screen_height/2 - image.height()/2), # y offset
))
self.root.lift()
self.root.mainloop()
if __name__ == '__main__':
import win32gui as w
print('Started!')
while(True):
title = w.GetWindowText(w.GetForegroundWindow())
print(title)
cw = CrosshairWindow()
while 1:
time.sleep(1)
print(title)
title = w.GetWindowText(w.GetForegroundWindow())
if title == "Apex Legends":
key = True
else:
key = False
print(title)
if key==True:
cw.start()
else:
cw.root.destroy()
C:\ProgramData\Anaconda3\python.exe "C:/Users/Hosisora_Ling/Documents/Tencent Files/2374416274/FileRecv/crosshair.py"
Started!
新建文件夹 – C:\Users\Hosisora_Ling\Documents\Tencent Files\2374416274\FileRecv\crosshair.py
Traceback (most recent call last):
File "C:\Users\Hosisora_Ling\Documents\Tencent Files\2374416274\FileRecv\crosshair.py", line 41, in <module>
cw.start(0)
File "C:\Users\Hosisora_Ling\Documents\Tencent Files\2374416274\FileRecv\crosshair.py", line 33, in start
self.root.lift()
File "C:\ProgramData\Anaconda3\lib\tkinter\__init__.py", line 1026, in tkraise
self.tk.call('raise', self._w, aboveThis)
_tkinter.TclError: can't invoke "raise" command: application has been destroyed
进程已结束,退出代码1#program ended with exit code 1
Please help point out the mistakes or a posible way to achieve my goal!

updating text after running mainloop in tkinter

I want to change the text displaying in frame after my mainloop() has been called. I have created loginfo function to append text in my string but nothing happens. The GUI gets started and displays the text originally contained in it("hi"), I don't see the text I add through loginfo function ("hello") and after exiting the GUI I get the below error.
Traceback (most recent call last):
File "1.py", line 5, in <module>
monitor.loginfo()
File "/home/shreyas/Desktop/test/main.py", line 45, in loginfo
self.text.configure(state='normal')
File "/usr/lib/python3.8/tkinter/__init__.py", line 1637, in configure
return self._configure('configure', cnf, kw)
File "/usr/lib/python3.8/tkinter/__init__.py", line 1627, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: invalid command name ".!frame.!text"
My task is to create a function that i will call any time with the text i want to insert.
The function will be called after the mainloop is running as i recieve the text to display.
These are the 2 files I created:
main.py
import tkinter
from tkinter import *
class Monitor:
def __init__(self):
self.root = Tk()
self.root.title('Monitor')
self.root.geometry("800x400")
self.root.grid_columnconfigure((0,1), weight=1)
self.root.grid_rowconfigure(0, weight=1)
"""-----------------------------------------------"""
self.console = Frame(self.root,borderwidth=1)
self.console.grid(row = 0, column = 0, sticky = W+E+N+S)
self.console.grid_columnconfigure(0, weight=1)
self.console.grid_rowconfigure(2, weight=1)
self.lbl_c = Label(self.console, text="console",bg='white')
self.lbl_c.grid(row = 1, column = 0, sticky = W+E+N+S)
self.text = tkinter.Text(self.console)
self.text.grid(row = 2, column = 0,rowspan = 3, columnspan = 1, sticky = N+S+E+W)
self.text.insert(tkinter.END,"hi")
self.text.configure(state='disabled')
"""------------------------------------------------"""
self.fm = Frame(self.root,borderwidth=1)
self.fm.grid(row = 0, column = 1, sticky = W+E+N+S)
self.fm.grid_columnconfigure(0, weight=1)
self.fm.grid_rowconfigure(2, weight=1)
self.lbl_fm = Label(self.fm, text="frequency_monitor",bg='white')
self.lbl_fm.grid(row = 1, column = 0, sticky = W+E+N+S)
self.text1 = tkinter.Text(self.fm)
self.text1.grid(row = 2, column = 0,rowspan = 1, columnspan = 1, sticky = N+S+E+W)
self.text1.insert(tkinter.END,"<---------- Frequency Monitor ---------->\n\n"+"Camera100\n"+"Frequency: 9.6 CPU Time: 3.0ms\n"+("----------------------------------------")+"Screen100\n"+"Frequency: 29.8 CPU Time: 6.0ms\n"+("----------------------------------------"))
self.text1.configure(state='disabled')
def loginfo(self):
self.text.configure(state='normal')
self.text.insert(tkinter.END,"hello")
self.text.update()
self.text.configure(state='disabled')
1.py
import main as m
monitor = m.Monitor()
monitor.root.mainloop()
monitor.loginfo()
I use python 3.1 to run my code. Can someone please tell me what's causing the error and how could I achieve the expected result.
update:
when i use mainloop() like so
import main as m
monitor = m.Monitor()
monitor.root.mainloop()
monitor.root.update()
monitor.root.update_idletasks()
monitor.loginfo()
i get the same error but when i use while
import main as m
monitor = m.Monitor()
#monitor.root.mainloop()
#monitor.loginfo()
while True:
monitor.root.update()
monitor.root.update_idletasks()
monitor.loginfo()
it updates text and keeps updating it since i called loginfo in while But it doesnot update if i call it outside the while loop.
The code after mainloop() gets called only after your applications is closed. So after the application is closed, the method is called, but the widgets used in the method is destroyed. So change your code to:
monitor = Monitor()
monitor.loginfo()
monitor.root.mainloop()
This way, the function is called before you exit the GUI. Think of mainloop() as a while loop that keeps updating till the window is closed. Technically saying mainloop() is same as:
while True: # Only exits, because update cannot be used on a destroyed application
root.update()
root.update_idletasks()
Edit:
Since you wanted a delay, you have to add a button or something that calls this method while the GUI is active, an example is to use after to show the function after some time, like:
monitor = Monitor()
monitor.root.after(1000,monitor.loginfo) # Cause a delay of 1 second or 1000 millisecond
monitor.root.mainloop()

Why will math.floor not work?

The goal of this program is to generate a sine wave using a 12 bit DAC. The code is as follows:
from Tkinter import *
import smbus, time, math, random
bus = smbus.SMBus(1)
address = 0x60
t=time.time()
class RPiRFSigGen:
# Build Graphical User Interface
def __init__(self, master):
self.start
frame = Frame(master, bd=10)
frame.pack(fill=BOTH,expand=1)
# set output frequency
frequencylabel = Label(frame, text='Frequency (Hz)', pady=10)
frequencylabel.grid(row=0, column=0)
self.frequency = StringVar()
frequencyentry = Entry(frame, textvariable=self.frequency, width=10)
frequencyentry.grid(row=0, column=1)
# Start button
startbutton = Button(frame, text='Enter', command=self.start)
startbutton.grid(row=1, column=0)
def start(self):
#self.low_freq=IntVar
low_freq = float(self.frequency.get())
out = 4095/2 + (math.sin(2*math.pi*low_freq*t))
#out = math.floor(out)
int(math.floor(out))
print (out)
bus.write_byte_data(address,0,out)
sendFrequency(low_freq)
# Assign TK to root
root = Tk()
# Set main window title
root.wm_title('DAC Controller')
root.geometry('250x150+650+250')
# Create instance of class RPiRFSigGen
app = RPiRFSigGen(root)
# Start main loop and wait for input from GUI
root.mainloop()
When I run the program I receive the following error after the value "out" is printed:
2046.18787764
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1437, in __call__
return self.func(*args)
File "/home/pi/DAC Controller.py", line 40, in start
bus.write_byte_data(address,0,out)
TypeError: integer argument expected, got float
It would appear that int(math.floor(out)) is not converting out to an integer because "out" is being printed as float still. Any suggestions?
int(math.floor(out))
This will create an integer version of out, but you aren't assigning it to anything, so it just gets discarded. If you want the changes to be reflected in the value of out, try:
out = int(math.floor(out))

Categories

Resources