Python: update tkinter canvas widget from thread events - python

I am trying to make a status light on my tkinter GUI. At this point I just want it to rotate from green to red to show that the script hasn't frozen. The python traceback errors that I get all point to __libraries that I don't understand. I feel like this must be a namespace problem, but I'm ripping out my hair trying to put my finger on it.
The eventCheck() method worked great at creating a label that toggled between 0 and 1 before I created the canvas object and tried passing c into it. There is so little information out there on what I am trying to do, maybe there is a better way?
Here is a condensed version of my script:
import tkinter as tk
from tkinter import Canvas
import time
import threading
class myGUI(tk.Frame):
def __init__(self, master, event):
self.master = master
self.event = event
super().__init__(master)
self.label = tk.Label(self, text="")
self.label.grid()
self.after(1, self.eventCheck)
c = tk.Canvas(self, bg='white', width=80, height=80)
c.grid()
self.eventCheck(c)
def redCircle(self, c):
c.create_oval(20, 20, 80, 80, width=0, fill='red')
print("redCircle Called")
def greenCircle(self,c):
c.create_oval(20, 20, 80, 80, width=0, fill='green')
print("greenCircle Called")
def eventCheck(self, c):
self.label['text'] = self.event.is_set()
if self.label['text'] == 0:
self.redCircle(c)
else:
self.greenCircle(c)
self.after(2000, self.eventCheck(c))
def timingLoop(event):
while True:
event.set()
time.sleep(2)
event.clear()
time.sleep(2)
def main():
root = tk.Tk()
root.title("myFirst GUI")
event = threading.Event()
t=threading.Thread(target=timingLoop, args=(event,))
t.daemon = True
t.start()
app = myGUI(root, event)
root.mainloop()
if __name__=="__main__":
main()

I found two major issues with your code. First, this isn't doing what you think it should:
def eventCheck(self, c):
# ...
self.after(2000, self.eventCheck(c))
Because you passed the result of a call to self.eventCheck(c) to after() instead of the method self.eventCheck, this is an infinite recursion that takes place immediately.
The second issue is that if you comment out all your timing and event stuff, your interface never actually comes up, so there's never anything to see. I've condensed (simplified) your example script even further into one that basically works:
import tkinter as tk
import threading
import time
class myGUI:
def __init__(self, master, event):
self.master = master
self.event = event
self.label = tk.Label(master, text="")
self.label.pack()
self.canvas = tk.Canvas(master, bg='white', width=80, height=80)
self.canvas.pack()
self.eventCheck()
def redCircle(self):
self.canvas.create_oval(20, 20, 80, 80, width=0, fill='red')
print("redCircle Called")
def greenCircle(self):
self.canvas.create_oval(20, 20, 80, 80, width=0, fill='green')
print("greenCircle Called")
def eventCheck(self):
flag = self.event.is_set()
self.label['text'] = flag
if flag:
self.greenCircle()
else:
self.redCircle()
self.master.after(2000, self.eventCheck)
def timingLoop(event):
while True:
event.set()
time.sleep(2)
event.clear()
time.sleep(2)
def main():
root = tk.Tk()
root.title("myFirst GUI")
event = threading.Event()
t = threading.Thread(target=timingLoop, args=(event,))
t.daemon = True
t.start()
app = myGUI(root, event)
root.mainloop()
if __name__ == "__main__":
main()
Now you should be able to add back your Frame superclass. Make sure to add the frame that is myGUI to your root object.

Related

Python Tkinter "stop" a while loop inside function

I have a Python program using Tkinter to show a value (var peso inside capturarpeso() function) in realtime.
But the while loop in capturarPeso() doesn't work, the loop only works the first time then the script is "waiting".
If I remove the TK component, it works perfectly. I simplified the script:
import tkinter as tk
from tkinter import *
import threading
import random
def capturarPeso():
global peso
while True:
peso = random.randrange(0, 101, 2)
print (peso)
return(peso)
def capturarPesoHilo():
hilo = threading.Thread(target=capturarPeso, name=None, group=None, args=(), kwargs=None, daemon=True)
hilo.start()
hilo.join()
class ActualizarPeso(Label):
def __init__(self, parent, *args, **kwargs):
Label.__init__(self, parent, *args, **kwargs)
self.tick()
def tick(self):
self.config(text= peso)
self.after(500, self.tick)
capturarPesoHilo()
window = tk.Tk()
window.title('Capturador pesos')
window.resizable(width=False, height=False)
pesoLabel = ActualizarPeso(window, font="Arial 60", fg="red", bg="black", width=8, height= 1)
pesoLabel.grid(row=15, column=0)
window.mainloop()
Any ideas on how to continue? Thank you
The function captuarPeso() has a return statement which will exit the while loop, this is why you only get 1 number printed to the screen.
Removing the return makes it so your program is stuck in that while loop which only prints peso because when you do hilo.join() to a thread it's actually waiting for the thread to exit before continuing, and since we got rid of the return in the first step, the thread never exits and so it's again stuck in a loop. To fix this I changed your while loop to while self.peso != -999: and after calling .mainloop() you set self.peso = -999 which will tell the program: the user has exited the Tkinter interface, exit my loop.
Since you used a class to put some of your tkinter gui in, why not put it all in? Generaly most people would put the entire tkinter interface in a class, I've gone ahead and restructured the program for you but tried to leave as much as the original by itself so you can analyze it and see how it works.
import tkinter as tk
import threading
import random
import time
class ActualizarPeso:
def __init__(self):
self.window = tk.Tk()
self.window.title('Capturador pesos')
self.window.resizable(width=False, height=False)
self.pesoLabel = self.crearLabel()
self.peso = 0
self.tick()
hilo1 = self.capturarPesoHilo()
self.window.mainloop()
self.peso = -999
hilo1.join()
def crearLabel(self):
pesoLabel = tk.Label(self.window, font="Arial 60", fg="red", bg="black", width=8, height=1)
pesoLabel.grid(row=15, column=0)
return pesoLabel
def tick(self):
self.pesoLabel.config(text=self.peso)
self.pesoLabel.after(500, self.tick)
def capturarPeso(self):
while self.peso != -999:
self.peso = random.randrange(0, 101, 2)
print(self.peso)
time.sleep(1)
def capturarPesoHilo(self):
hilo = threading.Thread(target=self.capturarPeso)
hilo.start()
return hilo
ActualizarPeso()
Let me know if you need something explained, and Happy Holidays!

Python tkinter threading and window refresh

I'm using python and tkinter to build a visualization tool that can refresh and visualize an updating object. Right now, the object can't change because the threading is not working. Any help or general knowledge would be appreciated. I'm relatively new to threading and tkinter.
example object I want to ingest
class color1:
def __init__(self, color):
self.color = color
def change_col(self, new_color):
self.color = new_color
def pass_col(self):
return(self)
my visualization code
class my_visual(threading.Thread):
def __init__(self, col1):
threading.Thread.__init__(self)
self.start()
self.col1 = col1
def viz(self):
self.root = Tk()
btn1 = Button(self.root, text = 'Refresh', command = self.refresh)
btn1.pack()
frame = Frame(self.root, width = 100, height = 100, bg = self.col1.color)
frame.pack()
btn2 = Button(self.root, text = 'Close', command = self.exit)
btn2.pack()
self.root.mainloop()
def refresh(self):
self.root.quit()
self.root.destroy()
self.col1 = self.col1.pass_col()
self.viz()
def exit(self):
self.root.quit()
self.root.destroy()
Code that works
c = color1('RED')
test = my_visual(c)
test.viz()
Code that doesn't work
In this version, the refresh works, but the threading doesn't. When the threading is working, the refresh won't pick up that the object has changed.
c.change_col('BLUE')
If you extend the threading.Thread class you need to override the run() method with your custom functionality. With no run method, the thread dies immediately. You can test whether a thread is alive with my_visual.is_alive().
The problem is that your test.viz() is an infinite loop because of self.root.mainloop(), so you cannot do anything once you called that function. The solution is to use a thread for test.viz(), and your thread for the class my_visual is no more necessary.
I added a time.sleep of 2 seconds before the refresh makes it blue, otherwise the color is blue at beginning.
Here you go :
import threading
from tkinter import *
import time
class color1:
def __init__(self, color):
self.color = color
def change_col(self, new_color):
self.color = new_color
def pass_col(self):
return(self)
class my_visual():
def __init__(self, col1):
self.col1 = col1
def viz(self):
self.root = Tk()
btn1 = Button(self.root, text = 'Refresh', command = self.refresh)
btn1.pack()
frame = Frame(self.root, width = 100, height = 100, bg = self.col1.color)
frame.pack()
btn2 = Button(self.root, text = 'Close', command = self.exit)
btn2.pack()
self.root.mainloop()
def refresh(self):
self.root.quit()
self.root.destroy()
self.col1 = self.col1.pass_col()
print("self.col1", self.col1, self.col1.color)
self.viz()
def exit(self):
self.root.quit()
self.root.destroy()
c = color1('RED')
test = my_visual(c)
t2 = threading.Thread(target = test.viz)
t2.start()
time.sleep(2)
print('Now you can change to blue when refreshing')
c.change_col('BLUE')

Keep subprocess as topmost on Linux OS in python3

I have a gui in python3 that I want to call another python script using subprocess (or something better?). I have the main gui full screen, which is what I want. The problem is, when I launch the subprocess, it initially becomes the topmost window on LXDE, so far so good. You can click on the main gui in the background which brings the main gui to topmost. This is what is expected from the window manager, but this covers the subprocess that I currently have blocking the main gui. I need to prevent the main gui from accepting focus while the subprocess is running or keeping the subprocess as topmost window.
maingui.py
#!/usr/bin/env python3
import tkinter as tk
import subprocess
def on_escape(event=None):
print("escaped")
root.destroy()
def do_something():
child = subprocess.run(["python3", "childgui.py"], shell=False)
######################################################################
busy=False
root = tk.Tk()
root.title("My GUI")
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
#don't run this as the subprocess blocks #root.attributes("-fullscreen", True) # run fullscreen
root.focus_set()
root.bind("<Escape>", on_escape)
#doesn't work#root.protocol("WM_TAKE_FOCUS", on_focus)
canvas = tk.Canvas(root)
canvas.grid(row=0)
cbutton = tk.Button(root, text = "Run Child", width=50, height=50, bg = "green", compound = "top", command = do_something)
lab = tk.Label(canvas, text = 'Output Here')
lab.grid(row=0, column=1)
cbutton.grid(row=1, column=0)
# --- start ---
root.mainloop()
childgui.py
from tkinter import *
class app(Frame):
def __init__(self, master):
Frame.__init__(self, master=None)
self.master.title("Child Process")
self.master.geometry("500x200")
Button(self.master, text="Grandchild", command=self.dialog).pack()
self.data = StringVar()
self.data.set("Here is the data")
Label(self.master, textvariable=self.data).pack()
def dialog(self):
d = MyDialog(self.master, self.data, "Grandchild", "Enter Data")
self.master.wait_window(d.top)
class MyDialog:
def __init__(self, parent, data, title, labeltext = '' ):
self.data = data
self.rt = parent
self.top = Toplevel(parent)
self.top.transient(parent)
self.top.grab_set()
self.top.geometry("300x100")
if len(title) > 0: self.top.title(title)
if len(labeltext) == 0: labeltext = 'Data'
Label(self.top, text=labeltext).pack()
self.top.bind("<Return>", self.ok)
self.e = Entry(self.top, text=data.get())
self.e.bind("<Return>", self.ok)
self.e.bind("<Escape>", self.cancel)
self.e.pack(padx=15)
self.e.focus_set()
b = Button(self.top, text="OK", command=self.ok)
b.pack(pady=5)
def ok(self, event=None):
print ("The data:", self.e.get())
self.data.set(self.e.get())
self.top.destroy()
def cancel(self, event=None):
self.top.destroy()
def main():
root = Tk()
a = app(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit: I should have mentioned the child gui is an app that is not mine. I only created this one for the example code to show the issue. While self.top.attributes may work, the actual child app is pretty large and I don't want to change it if I can avoid it. I don't have problems getting the focus, which is actually the problem. The main gui gets the focus back causing the subprocess to go behind. When the main gui is set to fullscreen (delete this to see #don't run this as the subprocess blocks #) the main gui is now stuck waiting for the child to close which you can't get to with the mouse

Display log lines on screen using parallel GUI

I'm looking for elegant way to display log lines on screen while the script is running.
from time import sleep
from threading import Thread
import tkinter as tk
class WaitGuiPrallel(Thread):
def __init__(self, TXT='Wait!', ttl='Logs'):
self.txt = TXT
Thread.__init__(self)
self.ttl = ttl
self.start() # This is starting the self.run()
def run(self):
self.root = tk.Tk()
self.root.attributes("-topmost", True)
self.root.title(self.ttl)
self.label = tk.Label(self.root, text=self.txt, font=("Helvetica", 20))
self.label.pack()
self.Location()
self.root.mainloop()
def Exit(self):
self.root.quit()
def Location(self):
w = 500 # width for the Tk root
h = 150 # height for the Tk root
ws = self.root.winfo_screenwidth() # width of the screen
self.root.geometry('%dx%d+%d+%d' % (w, h, ws - w - 20, 10))
def Update(self, newText):
self.txt1 = newText
self.label.destroy()
self.label = tk.Label(self.root, text=self.txt1,
font=("Helvetica", 12))
self.label.pack()
self.root.update()
Wait = WaitGuiPrallel(TXT='Wait! Do not touch mouse or keyboard')
sleep(2)
for t in range(5):
sleep(1)
Wait.Update(newText='Log line %s' % t)
Wait.Update(newText='Done!')
sleep(1)
Wait.Exit()
The current script got few issues:
it is not elegant - there must be a better way
it has problems when updated from different Threads
Sometime running it twice from Spyder+IPython is not possible (IPython freeze)
Tkinter doesn't really play well with threads. Using a StringVar is a little more thread friendly than other methods (in my experience). Here's how to do that plus a couple other fixes:
from time import sleep
from threading import Thread
import tkinter as tk
class WaitGuiPrallel(Thread):
def __init__(self, TXT='Wait!', ttl='Logs'):
Thread.__init__(self)
self.txt = TXT
self.ttl = ttl # what's this for?
self.daemon = True # this thread will terminate when the main thread terminates
self.start() # This is starting the self.run()
def run(self):
self.root = tk.Tk()
self.root.attributes("-topmost", True)
self.root.title(self.ttl)
self.txt = tk.StringVar(value=self.txt)
self.label = tk.Label(self.root, textvariable=self.txt, font=("Helvetica", 20))
self.label.pack()
self.Location()
self.root.mainloop()
def Location(self):
w = 500 # width for the Tk root
h = 150 # height for the Tk root
ws = self.root.winfo_screenwidth() # width of the screen
self.root.geometry('%dx%d+%d+%d' % (w, h, ws - w - 20, 10))
Wait = WaitGuiPrallel(TXT='Wait! Do not touch mouse or keyboard')
sleep(2)
for t in range(5):
sleep(1)
Wait.txt.set('Log line %s' % t)
Wait.txt.set('Done!')
sleep(1)
If you have multiple threads calling this then I would consider using a Queue and a third thread to monitor the queue.

Python GUI Threading

I want to have a function run continuously within a tkinter GUI. I have attached some shell code:
#!/usr/bin/env python3
import tkinter as tk
from time import sleep
import os
import sys
class Application(Frame):
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
......
root = Tk()
def run_continously:
... -- calls sleep -- ...
root.after(3000, run_continuously)
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
app = Application(root)
root.after(3000, run_continuously)
root.mainloop()
When running the GUI it tends to run the 'run_continuously' function once and the GUI freezes up. I suspect from poking around that this is due to the sleep function (which I call in the run_continuously function)
How would I go about implementing the 'run_continuously' function in a very simple thread to get around this issue? Would running the function in a thread even get me around the problem? The 'run_continuously' function does not need to interact at all with the Application class. I want it to run simply in the background and stop when the mainloop is finished.
Here is the end of the code:
def run_continuously(quit_flag):
print("in it")
if not quit_flag:
GPIO.output(DIR_PIN, True)
for i in range(steps):
print("in loop")
GPIO.output(STEP_PIN, True)
sleep(sDelay)
GPIO.output(STEP_PIN, False)
sleep(sDelay)
sleep(wait_time)
GPIO.output(DIR_PIN, False)
for i in range(steps):
GPIO.output(STEP_PIN, True)
sleep(sDelay)
GPIO.output(STEP_PIN, False)
sleep(sDelay)
print("run motor")
root.after(1000, run_continuously(quit_flag,))
#=================================================================
# main
#=================================================================
root = Tk() # Create the GUI root object
press1 = StringVar()
press2 = StringVar()
x = 275
y = 50
w = 580
h = 250
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
app = Application(root) # Create the root application window
quit_flag = False
root.after(0, app.read_pressure)
motor_thread = threading.Thread(target=run_continuously, args=(quit_flag,)).start()
root.mainloop()
quit_flag=True
motor_thread.join()
This is a minimal, complete, and verifiable example that exits cleanly if the 'QUIT' button is pressed or Ctrl-C is pressed:
from Tkinter import *
import multiprocessing
import threading
import time
import logging
class Application(Frame):
def create_widgets(self):
self.quit_button = Button(self)
self.quit_button['text'] = 'QUIT'
self.quit_button['fg'] = 'red'
self.quit_button['command'] = self.quit
self.quit_button.pack({'side': 'left'})
def __init__(self, master=None):
Frame.__init__(self, master)
self.quit_button = None
self.pack()
self.create_widgets()
self.poll()
def poll(self):
"""
This method is required to allow the mainloop to receive keyboard
interrupts when the frame does not have the focus
"""
self.master.after(250, self.poll)
def worker_function(quit_flag):
counter = 0
while not quit_flag.value:
counter += 1
logging.info("Work # %d" % counter)
time.sleep(1.0)
format = '%(levelname)s: %(filename)s: %(lineno)d: %(message)s'
logging.basicConfig(level=logging.DEBUG, format=format)
root = Tk()
app = Application(master=root)
quit_flag = multiprocessing.Value('i', int(False))
worker_thread = threading.Thread(target=worker_function, args=(quit_flag,))
worker_thread.start()
logging.info("quit_flag.value = %s" % bool(quit_flag.value))
try:
app.mainloop()
except KeyboardInterrupt:
logging.info("Keyboard interrupt")
quit_flag.value = True
logging.info("quit_flag.value = %s" % bool(quit_flag.value))
worker_thread.join()

Categories

Resources