How to send subprocess text output to a Tkinter text widget? - python

import subprocess
import os
import time
from tkinter import *
root=Tk()
textbox=Text(root)
textbox.pack()
def redirector(inputStr):
textbox.insert(INSERT, inputStr)
def address_ping():
'''
DOCSTRING -> Ip addresses in servers.txt are
192.168.0.1 192.168.0.26
'''
while True:
with open('servers.txt', 'r') as f:
for ip in f:
result=subprocess.Popen(["ping", "-c", "7", "-n", "-W", "2", ip],stdout=f, stderr=f).wait()
if result:
print("ip address " + ip, "is inactive")
sys.stdout.write = redirector
else:
print("ip address " + ip, "is active")
sys.stdout.write = redirector
pass
address_ping()
root.mainloop()
I am writing a piece of code here that will send a ping to an IP address and return a result. It works fine on the CLI, however I wish to "print" it to a Text widget using Tkinter. I am at a point where it will send it to the Text widget GUI but it only makes itself visible after I interrupt the program. I want to have a rolling output to the GUI text area as the pings progress through a loop.

Here's something that uses multithreading that seems to do what you want. The main program is split into a portion that handles the QUI and a separate workerthread that manages the ping subprocess, collecting the results from it and putting them in a Queue whose contents periodically get transferred to the GUI.
It uses time.sleep() because it's done in a separate thread that's not using tkinter so it's OK.
Note, seems likely you'll might want to add a vertical scrollbar to the GUI.
import subprocess
import queue
import threading
import time
import tkinter as tk
class GuiPart:
def __init__(self, master, queue, end_command):
self.queue = queue
self.master = master
self.textbox = tk.Text(root)
self.textbox.pack()
btn = tk.Button(master, text='Quit', command=end_command)
btn.pack(expand=True)
def process_incoming(self):
""" Handle all messages currently in the queue. """
while self.queue.qsize():
try:
info = self.queue.get_nowait()
self.textbox.insert(tk.INSERT, info)
except queue.Empty: # Shouldn't happen.
pass
class ThreadedClient:
""" Launch the main part of the GUI and the worker thread.
periodic_call() and end_application() could reside in the GUI part, but
putting them here keeps all the thread controls in a single place.
"""
def __init__(self, master):
self.master = master
self.queue = queue.Queue()
# Set up the GUI part.
self.gui = GuiPart(master, self.queue, self.end_application)
# Set up the background processing thread.
self.running = True
self.thread = threading.Thread(target=self.workerthread)
self.thread.start()
# Start periodic checking of the queue.
self.periodic_call(200)
def periodic_call(self, delay):
""" Every delay ms process everything new in the queue. """
self.gui.process_incoming()
if not self.running:
sys.exit(1)
self.master.after(delay, self.periodic_call, delay)
# Runs in separate thread - NO tkinter calls allowed.
def workerthread(self):
while self.running:
with open('servers.txt', 'r') as file:
for ip in file:
rc = subprocess.Popen(["ping", "-c", "7", "-n", "-W", "2", ip]).wait()
if rc:
self.queue.put('ip address {} is inactive\n'.format(ip))
time.sleep(1)
def end_application(self):
self.running = False # Stop queue checking.
self.master.quit()
if __name__ == '__main__':
root = tk.Tk()
root.title('Pinger')
client = ThreadedClient(root)
root.mainloop() # Display application window and start tkinter event loop.

Related

tkinter, master window does not loop after I start a thread [duplicate]

My interface is freezing on pressing the button. I am using threading but I am not sure why is still hanging. Any help will be appreciated. Thanks in advance
class magic:
def __init__(self):
self.mainQueue=queue.Queue()
def addItem(self,q):
self.mainQueue.put(q)
def startConverting(self,funcName):
if(funcName=="test"):
while not self.mainQueue.empty():
t = Thread(target = self.threaded_function)
t.start()
t.join()
def threaded_function(self):
time.sleep(5)
print(self.mainQueue.get())
m=magic()
def helloCallBack():
m.addItem("asd")
m.startConverting("test") //this line of code is freezing
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
top.mainloop()
Here's a recipe for doing an asynchronous task with a tkinter-based GUI. I adapted it from a recipe in the cited book. You should be able to modify it to do what you need.
To keep the GUI responsive requires not interfering with its mainloop() by doing something like join()ing a background thread—which makes the GUI "hang" until the thread is finished. This is accomplished by using the universal after() widget method to poll a Queue at regular intervals.
# from "Python Coobook 2nd Edition", section 11.9, page 439.
# Modified to work in Python 2 & 3.
from __future__ import print_function
try:
import Tkinter as tk, time, threading, random, Queue as queue
except ModuleNotFoundError: # Python 3
import tkinter as tk, time, threading, random, queue
class GuiPart(object):
def __init__(self, master, queue, end_command):
self.queue = queue
# Set up the GUI
tk.Button(master, text='Done', command=end_command).pack()
# Add more GUI stuff here depending on your specific needs
def processIncoming(self):
""" Handle all messages currently in the queue, if any. """
while self.queue.qsize():
try:
msg = self.queue.get_nowait()
# Check contents of message and do whatever is needed. As a
# simple example, let's print it (in real life, you would
# suitably update the GUI's display in a richer fashion).
print(msg)
except queue.Empty:
# just on general principles, although we don't expect this
# branch to be taken in this case, ignore this exception!
pass
class ThreadedClient(object):
"""
Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means that you have all the thread controls in a single place.
"""
def __init__(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.end_application)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
self.running = True
self.thread1 = threading.Thread(target=self.worker_thread1)
self.thread1.start()
# Start the periodic call in the GUI to check the queue
self.periodic_call()
def periodic_call(self):
""" Check every 200 ms if there is something new in the queue. """
self.master.after(200, self.periodic_call)
self.gui.processIncoming()
if not self.running:
# This is the brutal stop of the system. You may want to do
# some cleanup before actually shutting it down.
import sys
sys.exit(1)
def worker_thread1(self):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select()'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
"""
while self.running:
# To simulate asynchronous I/O, create a random number at random
# intervals. Replace the following two lines with the real thing.
time.sleep(rand.random() * 1.5)
msg = rand.random()
self.queue.put(msg)
def end_application(self):
self.running = False # Stops worker_thread1 (invoked by "Done" button).
rand = random.Random()
root = tk.Tk()
client = ThreadedClient(root)
root.mainloop()
For anyone having a problem with sys.exit(1) in #martineau's code - if you replace sys.exit(1) with self.master.destroy() the program ends gracefully. I lack the reputation to add a comment, hence the seperate answer.

How to check periodically if a thread is finished

I am trying to run a script which runs Asynchronously using threadings. I have run into an issue on how to check periodically if a thread is still alive (under start_thread1). I don't want to use join() as that will freeze the GUI until the threads is finished.
If that is not possible, I am open-minded with any other ways of doing it.
Here is the code I am using - this is a part of part of the code just to outline the "issue" that I have:
from tkinter.constants import LEFT, RIGHT, S
import tkinter.messagebox
from matplotlib import pyplot as plt
import tkinter as tk, time, threading, random, queue
class GuiPart(object):
def __init__(self, master, queue, queue2, client_instance):
self.queue = queue
self.queue2 = queue2
self.x = []
self.y= []
# Set up the GUI
self.Button2 = tk.Button(master, text="Button2", padx=10,
pady=5, fg="white", bg="#263D42", command=client_instance.start_thread1)
self.Button2.pack(side = RIGHT)
def processIncoming(self):
""" Handle all messages currently in the queue, if any. """
while not self.queue.empty():
msg = self.queue.get_nowait()
self.x.append(msg)
print(msg)
while not self.queue2.empty():
msg2 = self.queue2.get_nowait()
self.y.append(msg2)
fig, ax = plt.subplots()
ax.plot(self.x, self.y)
plt.show()
class ThreadedClient(object):
"""
Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means that you have all the thread controls in a single place.
"""
def __init__(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue = queue.Queue()
self.queue2 = queue.Queue()
self.running=True
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.queue2, self)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
def start_thread1(self):
thread1=threading.Thread(target=self.worker_thread1)
thread1.start()
# how to check periodically if the thread is finished and when is finished run self.gui.processIncoming()
# if I run it straight away like this the self.gui.processIncoming() will run before the thread will finish and nothing will be plotted
if thread1.is_alive() == False:
self.gui.processIncoming()
def worker_thread1(self):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select()'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
"""
if self.running:
time.sleep(5) # I am using time.sleep(5) just to simulate a long-running process
for i in range(20):
msg = i
self.queue.put(msg)
for j in range(20):
msg2 = j
self.queue2.put(msg2)
root = tk.Tk()
root.title('Matplotlib threading')
client = ThreadedClient(root)
root.mainloop()
If I understand correctly, you have a tkinter program with a second thread that does some data collection, and you need to get data from that second thread back into the gui. You can't simply wait for the second thread to finish because that would block the tkinter main loop.
One solution is to create a callback function, pass it to the second thread, and call it as the very last step in the second thread. Most tkinter objects aren't threadsafe, so if you're going to update the GUI in the callback function, you have to run the callback in the main thread. To do this, base the callback on tkinter's after_idle function. This causes the callback to occur in tk's event loop, in the main thread, much like a tkinter event handler.
This program does that, and is similar to your program. I changed a few minor things to make my static type checker (pylint) happy. I don't use matplotlib so I took that code out.
The important stuff is in start_thread1. The function f is declared and passed as an argument to the thread. Note that f doesn't call processIncoming, but passes it to after_idle; that instructs the tk main loop to perform the actual call. The function that got passed to worker_thread1 is called as the last step in the thread.
The end result is that processIncoming() is fired into the main thread when the worker thread finishes.
from tkinter.constants import RIGHT
import tkinter as tk
import time
import threading
import queue
class GuiPart:
def __init__(self, master, queue1, queue2, client_instance):
self.queue1 = queue1
self.queue2 = queue2
self.x = []
self.y= []
# Set up the GUI
self.Button2 = tk.Button(master, text="Button2", padx=10,
pady=5, fg="white", bg="#263D42",
command=client_instance.start_thread1)
self.Button2.pack(side = RIGHT)
def processIncoming(self):
""" Handle all messages currently in the queue, if any. """
print(threading.current_thread())
while not self.queue1.empty():
msg = self.queue1.get_nowait()
self.x.append(msg)
print("X", msg)
while not self.queue2.empty():
msg2 = self.queue2.get_nowait()
self.y.append(msg2)
print("Y", msg2)
print("Make a plot now")
class ThreadedClient:
"""
Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means that you have all the thread controls in a single place.
"""
def __init__(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue1 = queue.Queue()
self.queue2 = queue.Queue()
self.running=True
# Set up the GUI part
self.gui = GuiPart(master, self.queue1, self.queue2, self)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
def start_thread1(self):
def f():
self.master.after_idle(self.gui.processIncoming)
thread1=threading.Thread(target=self.worker_thread1, args=(f, ))
thread1.start()
def worker_thread1(self, callback):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select()'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
"""
print(threading.current_thread())
if self.running:
time.sleep(1) # simulate a long-running process
for i in range(20):
msg = i
self.queue1.put(msg)
for j in range(20):
msg2 = j
self.queue2.put(msg2)
callback()
root = tk.Tk()
root.title('Matplotlib threading')
client = ThreadedClient(root)
root.mainloop()

Freezing/Hanging tkinter GUI in waiting for the thread to complete

My interface is freezing on pressing the button. I am using threading but I am not sure why is still hanging. Any help will be appreciated. Thanks in advance
class magic:
def __init__(self):
self.mainQueue=queue.Queue()
def addItem(self,q):
self.mainQueue.put(q)
def startConverting(self,funcName):
if(funcName=="test"):
while not self.mainQueue.empty():
t = Thread(target = self.threaded_function)
t.start()
t.join()
def threaded_function(self):
time.sleep(5)
print(self.mainQueue.get())
m=magic()
def helloCallBack():
m.addItem("asd")
m.startConverting("test") //this line of code is freezing
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
top.mainloop()
Here's a recipe for doing an asynchronous task with a tkinter-based GUI. I adapted it from a recipe in the cited book. You should be able to modify it to do what you need.
To keep the GUI responsive requires not interfering with its mainloop() by doing something like join()ing a background thread—which makes the GUI "hang" until the thread is finished. This is accomplished by using the universal after() widget method to poll a Queue at regular intervals.
# from "Python Coobook 2nd Edition", section 11.9, page 439.
# Modified to work in Python 2 & 3.
from __future__ import print_function
try:
import Tkinter as tk, time, threading, random, Queue as queue
except ModuleNotFoundError: # Python 3
import tkinter as tk, time, threading, random, queue
class GuiPart(object):
def __init__(self, master, queue, end_command):
self.queue = queue
# Set up the GUI
tk.Button(master, text='Done', command=end_command).pack()
# Add more GUI stuff here depending on your specific needs
def processIncoming(self):
""" Handle all messages currently in the queue, if any. """
while self.queue.qsize():
try:
msg = self.queue.get_nowait()
# Check contents of message and do whatever is needed. As a
# simple example, let's print it (in real life, you would
# suitably update the GUI's display in a richer fashion).
print(msg)
except queue.Empty:
# just on general principles, although we don't expect this
# branch to be taken in this case, ignore this exception!
pass
class ThreadedClient(object):
"""
Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means that you have all the thread controls in a single place.
"""
def __init__(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.end_application)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
self.running = True
self.thread1 = threading.Thread(target=self.worker_thread1)
self.thread1.start()
# Start the periodic call in the GUI to check the queue
self.periodic_call()
def periodic_call(self):
""" Check every 200 ms if there is something new in the queue. """
self.master.after(200, self.periodic_call)
self.gui.processIncoming()
if not self.running:
# This is the brutal stop of the system. You may want to do
# some cleanup before actually shutting it down.
import sys
sys.exit(1)
def worker_thread1(self):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select()'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
"""
while self.running:
# To simulate asynchronous I/O, create a random number at random
# intervals. Replace the following two lines with the real thing.
time.sleep(rand.random() * 1.5)
msg = rand.random()
self.queue.put(msg)
def end_application(self):
self.running = False # Stops worker_thread1 (invoked by "Done" button).
rand = random.Random()
root = tk.Tk()
client = ThreadedClient(root)
root.mainloop()
For anyone having a problem with sys.exit(1) in #martineau's code - if you replace sys.exit(1) with self.master.destroy() the program ends gracefully. I lack the reputation to add a comment, hence the seperate answer.

Python multiprocessing asynchronous callback

I'm writing a program with a GUI using TKinter, in which the user can click a button and a new process is started to perform work using multiprocess.Process. This is necessary so the GUI can still be used while the work is being done, which can take several seconds.
The GUI also has a text box where the status of the program is displayed when things happen. This is often straight forward, with each function calling an add_text() function which just prints text in the text box. However, when add_text() is called in the separate process, the text does not end up in the text box.
I've thought about using a Pipe or Queue, but that would require using some sort of loop to check if anything has been returned from the process and that would also cause the main (GUI) process to be unusable. Is there some way to call a function in one process that will do work in another?
Here's an simple example of what I'm trying to do
import time
import multiprocessing as mp
import tkinter as tk
textbox = tk.Text()
def add_text(text):
# Insert text into textbox
textbox.insert(tk.END, text)
def worker():
x = 0
while x < 10:
add_text('Sleeping for {0} seconds'.format(x)
x += 1
time.sleep(1)
proc = mp.Process(target=worker)
# Usually happens on a button click
proc.start()
# GUI should still be usable here
The asyncronous things actually require loop.
You could attach function to the TkInter's loop by using Tk.after() method.
import Tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.check_processes()
self.root.mainloop()
def check_processes(self):
if process_finished:
do_something()
else:
do_something_else()
self.after(1000, check_processes)
app=App()
I ended up using a multiprocessing.Pipe by using TKinter's after() method to perform the looping. It loops on an interval and checks the pipe to see if there's any messages from the thread, and if so it inserts them into the text box.
import tkinter
import multiprocessing
def do_something(child_conn):
while True:
child_conn.send('Status text\n')
class Window:
def __init__(self):
self.root = tkinter.Tk()
self.textbox = tkinter.Text()
self.parent_conn, child_conn = multiprocessing.Pipe()
self.process = multiprocessing.Process(target=do_something, args=(child_conn,))
def start(self):
self.get_status_updates()
self.process.start()
self.root.mainloop()
def get_status_updates()
status = self.check_pipe()
if status:
self.textbox.add_text(status)
self.root.after(500, self.get_status_updates) # loop every 500ms
def check_pipe():
if self.parent_conn.poll():
status = self.parent_conn.recv()
return status
return None

Dynamically updating Tkinter window based on serial data

I'm trying to write a program that gets data from a serial port connection and automatically updates the Tkinter window in real time based on that data.
I tried to create a separate thread for the window that periodically gets the current data from the main thread and updates the window, like this:
serialdata = []
data = True
class SensorThread(threading.Thread):
def run(self):
serial = serial.Serial('dev/tty.usbmodem1d11', 9600)
try:
while True:
serialdata.append(serial.readline())
except KeyboardInterrupt:
serial.close()
exit()
class GuiThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.root = Tk()
self.lbl = Label(self.root, text="")
def run(self):
self.lbl(pack)
self.lbl.after(1000, self.updateGUI)
self.root.mainloop()
def updateGUI(self):
msg = "Data is True" if data else "Data is False"
self.lbl["text"] = msg
self.root.update()
self.lbl.after(1000, self.updateGUI)
if __name == "__main__":
SensorThread().start()
GuiThread().start()
try:
while True:
# A bunch of analysis that sets either data = True or data = False based on serialdata
except KeyboardInterrupt:
exit()
Running it gives me this error:
Exception in thread Thread-2:
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 522, in __bootstrap_inner
self.run()
File "analysis.py", line 52, in run
self.lbl1.pack()
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-tk/Tkinter.py", line 1764, in pack_configure
+ self._options(cnf, kw))
RuntimeError: main thread is not in main loop
When I google this error, I mostly get posts where people are trying to interact with the window from two different threads, but I don't think I'm doing that. Any ideas? Thanks so much!
Don't run the TK gui from a thread - run it from the main process. I mashed your example into something that demonstrates the principle
from time import sleep
import threading
from Tkinter import *
serialdata = []
data = True
class SensorThread(threading.Thread):
def run(self):
try:
i = 0
while True:
serialdata.append("Hello %d" % i)
i += 1
sleep(1)
except KeyboardInterrupt:
exit()
class Gui(object):
def __init__(self):
self.root = Tk()
self.lbl = Label(self.root, text="")
self.updateGUI()
self.readSensor()
def run(self):
self.lbl.pack()
self.lbl.after(1000, self.updateGUI)
self.root.mainloop()
def updateGUI(self):
msg = "Data is True" if data else "Data is False"
self.lbl["text"] = msg
self.root.update()
self.lbl.after(1000, self.updateGUI)
def readSensor(self):
self.lbl["text"] = serialdata[-1]
self.root.update()
self.root.after(527, self.readSensor)
if __name__ == "__main__":
SensorThread().start()
Gui().run()
You need to put the GUI in the main thread, and use a separate thread to poll the serial port. When you read data off of the serial port you can push it onto a Queue object.
In the main GUI thread you can set up polling to check the queue periodically, by using after to schedule the polling. Call a function which drains the queue and then calls itself with after to effectively emulate an infinite loop.
If the data that comes from the sensor comes at a fairly slow rate, and you can poll the serial port without blocking, you can do that all in the main thread -- instead of pushing and pulling from the queue, your main thread can just see if there's data available and read it if there is. You can only do this if it's possible to read without blocking, otherwise your GUI will freeze while it waits for data.
For example, you could make it work like this:
def poll_serial_port(self):
if serial.has_data():
data = serial.readline()
self.lbl.configure(text=data)
self.after(100, self.poll_serial_port)
The above will check the serial port 10 times per second, pulling one item off at a time. You'll have to adjust that for your actual data conditions of course. This assumes that you have some method like has_data that can return True if and only if a read won't block.

Categories

Resources