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()
Related
I am trying to have two daemonic threads run alongside my tkinter main thread. One of the threads generates data and the other thread runs FuncAnimation that plots the data that is generated. However, I am having issues with getting FuncAnimation to run, I can see that whenever I click the button both print statements are shown in the console meaning FuncAnimation appears to have been skipped or ran but exited due to some missing or bad parameter. I checked the matplotlib documentation to ensure I'm not missing anything and I think I have created all the objects and called the right methods to make this happen but something is obviously amiss. Any help is greatly appreciated.
import tkinter as tk
import time
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
from matplotlib import animation
import threading
root = tk.Tk()
class dynamicChart():
def __init__(self):
self.x = []
self.y = []
self.buildChart()
def buildChart(self):
self.figure = Figure(figsize=(5, 5), dpi=100)
ax = self.figure.add_subplot(111)
self.scatter = ax.scatter(self.x,self.y)
canvas = FigureCanvasTkAgg(self.figure, root)
canvas.draw()
canvas.get_tk_widget().pack(pady=10)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack()
def animate(self,i):
print("Enter animate")
self.scatter.set_offsets([self.x, self.y])
def Ani(self):
print("Enter Ani")
self.animation = animation.FuncAnimation(self.figure, self.animate, interval = 2000)
print("Exit Ani")
class genData():
def __init__(self):
self.chart = dynamicChart()
def generate(self):
X = 2
Y = 2
i = 1
while True:
time.sleep(1)
X = X*i
Y = Y + i
self.chart.x.append(X)
self.chart.y.append(Y)
i+=1
StartDataGen_CreateFigureAxes = genData()
genData = threading.Thread(target = StartDataGen_CreateFigureAxes.generate, daemon = True)
genData.start()
ani = threading.Thread(target = StartDataGen_CreateFigureAxes.chart.Ani, daemon = True)
button = tk.Button(root, text="Graph It", command = lambda: ani.start())
button.pack()
root.mainloop()
I've searched the whole internet to answer my problem, but nobody seems to have the same one: I'm trying to update my tkinter GUI dynamically from a subprocess output, which works fine, if I'm starting my GUI inside eclipse. BUT if I'm running it in the file explorer or in visual studio, the 'stdout.readline' command waits, until the subprocess is finished. Only then the complete output is printed to my textarea... I am working with a thread and I've tried 2 ways: one is shown in 'App.py', the other one is threading the 'read_update' method instead (and not using 'reader_thread' and 'update' methods).
Interesting sidenote: the sys.argv command in Test.py does not return my string "var_test". Can anyone tell me why?
My Classes are:
GUI.py
import Tkinter as tk
from App import App
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.protocol("WM_DELETE_WINDOW", app.quit)
root.mainloop()
App.py
#App.py
import Tkinter as tk
import tkFont as tkfont
import subprocess
from subprocess import Popen
from subprocess import PIPE
from itertools import islice
from threading import Thread
from ttk import Scrollbar
from Tkinter import *
from Queue import Queue, Empty
class App():
def __init__(self, root):
self.root = root
self.root.title_font = tkfont.Font(family = "Helvetica", size = 18, weight = "bold", slant = "italic")
Grid.columnconfigure(self.root, 5, weight = 1)
button_ok = tk.Button(self.root, text = "OK", width = 10, command = lambda: self.on_okay())
button_ok.grid(row = 7, column = 0, padx = (20,0), pady = 10, sticky = W)
xscrollbar = Scrollbar(self.root, orient=HORIZONTAL)
xscrollbar.grid(row=8, column=1, columnspan=4, sticky=E + W)
yscrollbar = Scrollbar(self.root, orient=VERTICAL)
yscrollbar.grid(row=8, column=5, sticky=N + S)
self.textarea = Text(self.root, wrap=NONE, bd=0,
xscrollcommand=xscrollbar.set,
yscrollcommand=yscrollbar.set)
self.textarea.grid(row=8, column=1, columnspan=4, rowspan=1,
padx=0, sticky=E + W + S + N)
def on_okay(self):
self.textarea.delete("1.0", END)
exec_path = r"\Test.py" #insert location of Test.py
self.process = subprocess.Popen([exec_path, 'var_test'], shell = True, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
self.q = Queue(maxsize = 1024)
t = Thread(target=self.reader_thread, args=[self.q])
t.daemon = True
t.start()
self.update(self.q)
def reader_thread(self, q):
try:
with self.process.stdout as pipe:
for line in iter(pipe.readline, b''):
q.put(line)
finally:
q.put(None)
def update(self, q):
for line in self.iter_except(q.get_nowait, Empty):
if line is None:
#self.quit()
return
else:
self.textarea.insert(INSERT, line)
self.textarea.yview(END)
break
self.root.after(40, self.update, q)
def iter_except(self, function, exception):
try:
while True:
yield function()
except exception:
return
def read_update(self):
while True:
line = self.process.stdout.readline()
if line == "" and self.process.poll() != None:
break
elif line == "":
pass
else:
self.textarea.insert(INSERT, line)
self.textarea.yview(END)
self.textarea.update_idletasks()
def quit(self):
try:
self.process.kill()
except AttributeError:
pass
finally:
self.root.destroy()
Test.py
import sys
from time import sleep
var = sys.argv
print var
for i in range(1, 10):
print i
print "finished printing numbers"
sleep(10)
print "finished"
Thank you for your help! I'm pretty desperate 'cause I've been trying to solve this problems for many hours now...
use sys.stdout.flush() after print
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)
I have the following python code (in PyCharm) that I use to take readings from an Arduino board. The readings themselves are fine. I have the two following problems with the tkinter part of the code:
The code begins to read in values from Arduino as soon as it is
launched, whereas I want to initiate this on a button click
('read_data'); as long as I don't press the 'read_data' button, the
graph is not displayed, but readings are taken; I can see that when
I open the graph several seconds after I begin running the code;
When I close the plot close_plot, the graph window is indeed
closed, only to be re-opened a short while later.
I think the problem lies in Top.after as it is continuously run within the mainloop() and, therefore, the read_data function is continuously updated. How can I get around this?
import serial
from tkinter import *
from matplotlib import pyplot as plt
Top = Tk()
ser = serial.Serial('COM3', baudrate=9600, timeout=1)
x = []
y = []
def read_data():
plt.ion()
new_value = ser.readline().decode('ascii')
if new_value == '':
pass
else:
y.append(eval(new_value[:-2]))
x.append(len(y) - 1)
plt.plot(x, y, 'r-')
plt.show()
plt.pause(0.0001)
Top.after(100, read_data)
def close_plot():
plt.close()
global x, y
x = []
y = []
def quit():
Top.destroy()
Button(Top, text='Read', command=read_data).pack()
Button(Top, text='Close plot', command=close_plot).pack()
Button(Top, text='Quit', command=quit).pack()
Top.after(100, read_data)
mainloop()
Edit: when pressing the read_data button, I get the following warning:
C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\backend_bases.py:2445: MatplotlibDeprecationWarning: Using default event loop until function specific to this GUI is implemented warnings.warn(str, mplDeprecation)
First, remove the line:
Top.after(100, read_data)
that comes immediately before mainloop() like furas suggested.
Then add after_cancel method to stop calling read_data every 100 ms, but for that to work, we need to be assigning after we use inside the method to a global variable first:
func_id = Top.after(100, read_data)
and then finally call after_cancel in close_plot:
Top.after_cancel(func_id)
Your code should be exactly like below:
import serial
from tkinter import *
from matplotlib import pyplot as plt
Top = Tk()
ser = serial.Serial('COM3', baudrate=9600, timeout=1)
x = []
y = []
func_id = None
def read_data():
global func_id
plt.ion()
new_value = ser.readline().decode('ascii')
if new_value == '':
pass
else:
y.append(eval(new_value[:-2]))
x.append(len(y) - 1)
plt.plot(x, y, 'r-')
plt.show()
plt.pause(0.0001)
func_id = Top.after(100, read_data)
def close_plot():
global func_id
#to no longer update the plot
Top.after_cancel(func_id)
plt.close()
global x, y
del x[:]
del y[:]
def quit():
Top.destroy()
Button(Top, text='Read', command=read_data).pack()
Button(Top, text='Close plot', command=close_plot).pack()
Button(Top, text='Quit', command=quit).pack()
mainloop()
I have been having lots of problems with this code, it is giving me a weird error. It occurs when I attempt to close the program; I get this error(shown at the bottom ). I also previously before adding the .protocol but was getting an error to do with the module. Is it the way I have imported tkinter? Or what I am attempting to destroy?
import tkinter.ttk
from tkinter.constants import *
from tkinter import *
class App(ttk.Frame):
#classmethod
def main(cls):
GUI = tkinter.Tk()
app = cls(GUI)
app.grid(sticky=NSEW)
GUI.grid_columnconfigure(0, weight=1)
GUI.grid_rowconfigure(0, weight=1)
GUI.resizable(True, False)
GUI.mainloop()
self.protocol("WM_DELETE_WINDOW", self.destroy())
GUI.protocol("WM_DELETE_WINDOW", GUI.destroy())
def __init__(self, GUI):
super().__init__(GUI)
self.create_variables()
self.create_widgets()
self.grid_widgets()
self.grid_columnconfigure(0, weight=1)
def create_variables(self):
pass
def create_widgets(self):
self.Logo = tkinter.PhotoImage(file="Logo.gif")
self.x = Label(image=self.Logo)
##Top bar Widgets##
self.button1 =ttk.Button(self, text="Profile", command=self.GetProfile)
if self.CheckLogin() == False:
self.button2 = ttk.Button(self, text="Log in", command=self.Login)
self.button3 = ttk.Button(self, text="Download",command=self.download)
self.Label2 = ttk.Label(self,text="")
def grid_widgets(self):
options = dict(sticky=NSEW, padx=3, pady=4)
options1 = dict(sticky=N)
self.x.grid(column=0,row=1, **options1)
#top bar
self.button1.grid(column = 1,row = 1,**options1)
self.button2.grid(column = 2,row = 1,**options1)
self.button3.grid(column = 3,row = 1,**options1)
#To be completed functions
def download(self):
pass
def GetProfile(self):
pass
def Login(self):
if self.Logindefault() == True:
print("login here")
elif self.Logindefault() == False:
self.v = StringVar()
print("Not logged in.")
options = dict(sticky=NSEW, padx=3, pady=4)
self.t = Toplevel(self)
self.t.title("Login")
self.t.grid_columnconfigure(0, weight=1)
self.t.grid_rowconfigure(0, weight=1)
self.t.entry1 = ttk.Entry(self.t)
self.t.entry2 = ttk.Entry(self.t)
self.t.button1 = ttk.Button(self.t,text="login",command=self.destroy)
self.t.entry1.grid(column = 0 ,row = 0, **options)
self.t.entry1.insert(0,"Username")
self.t.entry2.grid(column = 0 ,row = 1, **options)
self.t.entry2.insert(0,"Password")
self.t.button1.grid(column = 1,row = 0,rowspan=2, **options)
self.t.checkbox = ttk.Checkbutton(self.t,text="Remember me",variable=self.v)
self.t.checkbox.grid(column =0,row=2,**options)
def destroy(self):
self.usernameGO = self.t.entry1.get()
self.passwordGO = self.t.entry2.get()
print(self.usernameGO,self.passwordGO,self.v)
self.t.destroy()
def CheckLogin(self):
return False #If not logged in.
def Logindefault(self):
try:
file = open("UserLog.txt","rt")
for i in file:
if i[0:6] =="__usr__":
self.username = i.sptrip("__usr__")
elif i[0:6] =="__pss__":
self.password = i.strip("__pss__")
return True
except Exception:
#username file not found
print("error")
return False
if __name__ == '__main__':
App.main()
Here is the error which I get when I try to close the main window:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python34\lib\tkinter\__init__.py", line 1533, in __call__
return self.func(*args)
File "C:\Python34\lib\tkinter\__init__.py", line 1892, in destroy
for c in list(self.children.values()): c.destroy()
File "C:\Users\charlie\Desktop\Yahtzee - Copy.py", line 74, in destroy
self.usernameGO = self.t.entry1.get()
AttributeError: 'App' object has no attribute 't'
self.protocol("WM_DELETE_WINDOW", self.destroy())
GUI.protocol("WM_DELETE_WINDOW", GUI.destroy())
Generally, when you register callback methods, you need to omit the parentheses. Otherwise, the methods will be called immediately, and their return values will be registered instead. This means that destroy will be executed before Login executes, so self.t won't yet exist. Try:
self.protocol("WM_DELETE_WINDOW", self.destroy)
GUI.protocol("WM_DELETE_WINDOW", GUI.destroy)