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)