Home > Software engineering >  How can I write output from a Subprocess to a text widget in tkinter in realtime?
How can I write output from a Subprocess to a text widget in tkinter in realtime?

Time:03-03

Hey I want to display the output from a subprocess into my GUI in a tk.Text Widget. I have a Code and it works minimal but it is not in realtime. How can I achieve that the output from the Terminal will be written to the Text widget in Realtime???

import tkinter as tk
import subprocess
import threading

#### classes ####

class Redirect():
    
    def __init__(self, widget, autoscroll=True):
        self.widget = widget
        self.autoscroll = autoscroll

    def write(self, textbox):
        self.widget.insert('end', textbox)
        if self.autoscroll:
            self.widget.see('end') # autoscroll

    def flush(self):
        pass

def run():
    threading.Thread(target=test).start()

def test():

    p = subprocess.Popen("python myprogram.py".split(), stdout=subprocess.PIPE, bufsize=1, text=True)
    while p.poll() is None:
        msg = p.stdout.readline().strip() # read a line from the process output
        if msg:
            print(msg)


##### Window Setting ####

fenster = tk.Tk()
fenster.title("My Program")


textbox = tk.Text(fenster)
textbox.grid()

    
scrollbar = tk.Scrollbar(fenster, orient=tk.VERTICAL)
scrollbar.grid()

textbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=textbox.yview)

start_button= tk.Button(fenster, text="Start", command=run),
start_button.grid()
    

old_stdout = sys.stdout    
sys.stdout = Redirect(textbox)


fenster.mainloop()

sys.stdout = old_stdout

CodePudding user response:

welcome to stack overflow. I modified your code a bit (you had a , after defining start_button and didnt import sys, also i put your code below ##### Window Setting #### into a boilerplate-code).

Your main issue was, that you do not make your Text widget available in your run and furthermore in your test function (executed as a thread). So i handed over your widget as an argument to both functions (probably not the most pythonic way, however). For executing a command bound to a button i used from functools import partial and binded the command including an argument via command=partial(run, textbox). Then i simply handed over the argument in run to the thread with args=[textbox] in the line where you create & start the thread. Finally, i updated the textbox with textbox.insert(tk.END, msg "\n") in your test function while removing the print(). The insert appends any text at the end to the textbox, the "\n" starts a new line.

Here is the (slightly restructured) complete code (app.py):

import tkinter as tk
import subprocess
import threading
import sys
from functools import partial


# ### classes ####

class Redirect:
    
    def __init__(self, widget, autoscroll=True):
        self.widget = widget
        self.autoscroll = autoscroll

    def write(self, textbox):
        self.widget.insert('end', textbox)
        if self.autoscroll:
            self.widget.see('end') # autoscroll

    def flush(self):
        pass


def run(textbox=None):
    threading.Thread(target=test, args=[textbox]).start()


def test(textbox=None):

    p = subprocess.Popen("python myprogram.py".split(), stdout=subprocess.PIPE, bufsize=1, text=True)
    while p.poll() is None:
        msg = p.stdout.readline().strip()  # read a line from the process output
        if msg:
            textbox.insert(tk.END, msg   "\n")


if __name__ == "__main__":
    fenster = tk.Tk()
    fenster.title("My Program")
    textbox = tk.Text(fenster)
    textbox.grid()
    scrollbar = tk.Scrollbar(fenster, orient=tk.VERTICAL)
    scrollbar.grid()

    textbox.config(yscrollcommand=scrollbar.set)
    scrollbar.config(command=textbox.yview)

    start_button = tk.Button(fenster, text="Start", command=partial(run, textbox))
    start_button.grid()

    old_stdout = sys.stdout
    sys.stdout = Redirect(textbox)

    fenster.mainloop()
    sys.stdout = old_stdout

And here is the code of the test-file myprogram.py i created:

import time

for i in range(10):
    print(f"TEST{i}")
    time.sleep(1)
  • Related