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.
Related
Sorry for the confusing title basically what I'm trying to figure out how to import a frame from one script into another. I'm not sure how I would call it since it has so many functions. Here are my two scripts:
File Name - wrapper:
import tkinter as tk
import workingcatch
from tkinter import *
root = tk.Tk()
outputframe = LabelFrame(master=root, width=800, height=700) #where i want to import the script
outputframe.pack(side=LEFT, padx=10,pady=10)
root.geometry('1280x720')
root.mainloop()
File Name - workingcatch:
from subprocess import Popen, PIPE, STDOUT
from threading import Thread
import tkinter as tk
import logging
import time
import sys
info = logging.getLogger(__name__).info
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
platform = windows = 'mswin'
# define dummy subprocess to generate some output
cmd = [sys.executable or "python", "-u", "-c", """
import itertools, time
for i in itertools.count():
print(i)
time.sleep(0.5)
exit()
""", "exit"]
class OStream(Thread):
enable_print = True
def handel_line(self, line):
if platform == windows:
# Windows uses: "\r\n" instead of "\n" for new lines.
line = line.replace(b"\r\n", b"\n")
if self.enable_print:
info("got: %r", line)
if self.stream_print is not None:
self.stream_print(line)
def stop(self):
self.alive = False
def run(self):
while self.alive:
for s in self.ostrams:
line = s.stdout.readline()
if line:
self.handel_line(line)
time.sleep(0.2)
info("OStream Exit")
def pipe_proc(self, stream):
self.ostrams.append(stream)
def stream_callback(self, func):
self.stream_print = func
def __init__(self):
self.ostrams = []
self.alive = True
self.stream_print = None
Thread.__init__(self)
class Scrolable_Frame(tk.Frame):
def get(self):
return self.interior
def __init__(self, master):
tk.Frame.__init__(self, master)
self.scroll = tk.Scrollbar(self)
self.scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas = tk.Canvas(
self, bd=0, highlightthickness=0,
yscrollcommand=self.scroll.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.TRUE)
self.scroll.config(command=self.canvas.yview)
self.canvas.xview_moveto(0)
self.canvas.yview_moveto(0)
self.interior = tk.Frame(self.canvas)
interior_id = self.canvas.create_window(
0, 0, window=self.interior, anchor=tk.NW
)
def _configure_interior(_):
size = (self.interior.winfo_reqwidth(), self.interior.winfo_reqheight())
self.canvas.config(scrollregion="0 0 %s %s" % size)
if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
self.canvas.config(width=self.interior.winfo_reqwidth())
self.interior.bind('<Configure>', _configure_interior)
def _configure_canvas(_):
if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
self.canvas.itemconfigure(interior_id, width=self.canvas.winfo_width())
self.canvas.bind('<Configure>', _configure_canvas)
def _on_mousewheel(event):
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), 'units')
self.canvas.bind_all("<MouseWheel>", _on_mousewheel)
class ShowProcessOutputDemo(tk.Tk):
def __init__(self):
"""Start subprocess, make GUI widgets."""
tk.Tk.__init__(self)
self.geometry('300x200+500+300')
self.protocol("WM_DELETE_WINDOW", self.stop)
self.proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)
self.ostream = OStream()
self.ostream.pipe_proc(self.proc)
self.ostream.stream_callback(self.add_label)
self.ostream.start()
self.exit_button = tk.Button(
self, text="Stop subprocess", command=self.stop
)
self.exit_button.pack(pady=20)
self.scolable_frame = Scrolable_Frame(self)
self.scolable_frame.pack(
expand=True, fill=tk.BOTH, pady=20, padx=20
)
def add_label(self, line):
line_text = 'OStream line content: {0}'.format(line[:-1].decode())
tk.Label(
self.scolable_frame.get(), text=line_text
).pack(anchor=tk.CENTER, expand=True, fill=tk.X)
def stop(self):
"""Stop subprocess and quit GUI."""
self.ostream.stop()
self.proc.kill()
self.proc.stdout.close()
self.proc.wait(timeout=2)
info("GUI Exit")
self.quit()
if __name__ == '__main__':
app = ShowProcessOutputDemo()
app.mainloop()
So what I'm trying to do is get the output from "workingcatch" into 'wrapper's frame.
Thanks for reading
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')
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.
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()
I have a simple program with two classes, one controls a relay board via a serial.serial connection. The other class is for a GUI which will send commands to the relay class and then display the status of the relay board.
I'm having an issue with sending messages from the relay class to the tkinter class. The messages only appear once the relay command has finished. I've cut down my program below. Test.test() represents a function in my relay class where as the MainWindow class is my GUI.
Someone has pointed out to use threading to handle the messages being passed between the classes. Is that my only option? I have not delved into threading yet.
from Tkinter import *
import time
import ScrolledText
class Test():
def test(self):
main.textboxupdate(" test start ")
time.sleep(2)
main.textboxupdate(" test middle ")
time.sleep(2)
main.textboxupdate(" test end ")
class MainWindow(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.canvas = Canvas(width=1200,height=700)
self.canvas.pack(expand=YES,fill=BOTH)
self.frame = Frame(self.canvas)
self.TextBox = ScrolledText.ScrolledText(self.frame)
self.open = Button(self.frame, text="Open Cover",
command=test.test)
def createtextbox(self, statusmsg):
self.frame.place(x=0,y=0)
self.TextBox.config(state = NORMAL)
self.TextBox.insert(END, statusmsg,('error'))
self.TextBox.config(state = 'disabled', height = 2, width = 35)
self.TextBox.see(END)
self.TextBox.grid(columnspan=2, rowspan = 1)
self.open.grid()
def textboxupdate(self, statusmsg):
statusmsg = statusmsg +'\n'
self.TextBox.config(state = NORMAL)
self.TextBox.insert(END, statusmsg,('error'))
self.TextBox.config(state = 'disabled', height = 10, width = 50)
self.TextBox.see(END)
test = Test()
root = Tk()
main = MainWindow(root)
main.createtextbox('Startup\n')
root.mainloop()
Here's one option:
from Tkinter import *
import time
import ScrolledText
import threading, Queue
class Test():
def __init__(self):
self.msg_queue = Queue.Queue()
def test(self):
self.msg_queue.put(" test start ")
time.sleep(2)
self.msg_queue.put(" test middle ")
time.sleep(2)
self.msg_queue.put(" test end ")
class MainWindow(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.canvas = Canvas(width=1200,height=700)
self.canvas.pack(expand=YES,fill=BOTH)
self.frame = Frame(self.canvas)
self.TextBox = ScrolledText.ScrolledText(self.frame)
self.open = Button(self.frame, text="Open Cover",
command=self.create_thread)
self.test_thread = None
self.createtextbox("")
def create_thread(self):
self.test_thread = threading.Thread(target=test.test)
self.test_thread.start()
self.after(10, self.update_textbox)
def update_textbox(self):
while not test.msg_queue.empty():
self.textboxupdate(test.msg_queue.get())
if self.test_thread.is_alive():
self.after(10, self.update_textbox)
else:
self.test_thread = None
def createtextbox(self, statusmsg):
self.frame.place(x=0,y=0)
self.TextBox.config(state = NORMAL)
self.TextBox.insert(END, statusmsg,('error'))
self.TextBox.config(state = 'disabled', height = 2, width = 35)
self.TextBox.see(END)
self.TextBox.grid(columnspan=2, rowspan = 1)
self.open.grid()
def textboxupdate(self, statusmsg):
statusmsg = statusmsg +'\n'
self.TextBox.config(state = NORMAL)
self.TextBox.insert(END, statusmsg,('error'))
self.TextBox.config(state = 'disabled', height = 10, width = 50)
self.TextBox.see(END)
self.update_idletasks()
test = Test()
main = MainWindow()
main.pack()
main.mainloop()
The first change is that instead of calling a function, Test.test puts the messages onto a queue. Test.test is started in a separate thread by MainWindow.start_thread. MainWindow.start_thread also schedules a check on the thread by asking tkinter to call update_textbox after 10 milliseconds (self.after(10, self.update_textbox)). This function takes all the new messages off the queue, and displays them. Then, if the thread is still running, it reschedules itself, otherwise it resets the MainWindow.