2

I'm trying to make a very simple GUI for my program(here i have replaced it with the test function). I'm able to run my code via the gui, but the output for the program is being displayed in the terminal. I want the output to be visible in my original window instead.

Here is my code:

import tkinter as tk
from test import *
from tkinter import *
import os

root = tk.Tk()

canvas = tk.Canvas(root, height=400, width =550, bg ="#202020") 
canvas.pack()

#frame = tk.Frame(root, bg="white");
#frame.place(relwidth=0.8, relheight = 0.8, relx=0.1, rely=0.1)

topFrame = tk.Frame(root, bg="#202020")
topFrame.place(relwidth=1, relheight = 0.75)

bottomFrame = tk.Frame(root, bg="#202020")
bottomFrame.place(rely=0.75, relwidth=1, relheight = 0.25)


launch = tk.Button(bottomFrame, text="Launch", bg="white", fg="#202020", font="noah 10 bold", padx=20, command=test)
launch.place(in_=bottomFrame, rely=0.5, relx=0.5, anchor=CENTER)

root.mainloop()

and here is the other file I'm calling the function from :

import os
def test():
    print("Hello World")
    os.system("ping 192.168.0.1 -c 4")

Is there any way to capture the output of this function and display it in the topframe of my gui in realtime?

4
  • use tkinter widgets like Text or Label to display it - and then label["text"] = "Hello World". Eventually you can assign own class to sys.stdout and it can redirect print() to Label or Text. Commented Jun 12, 2020 at 1:18
  • if you want to display ping then you have to use subprocess (ie, subprocess.run()) instead of os.system() to catch displayed data and next you can put it in Label or Text Commented Jun 12, 2020 at 1:19
  • 3
    This may be helpful. Commented Jun 12, 2020 at 2:04
  • see also tkterminal, a Terminal widget for Tkinter Commented Apr 6, 2022 at 2:35

1 Answer 1

7

You can create class with function write() which insert text to tkinter.Text and assign its instance to sys.stdout - and then print() will send to tkinter.Text

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

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

    # some widget may need it
    #def flush(self):
    #    pass

and

text = tk.Text(root)
text.pack()

# keep original stdout
old_stdout = sys.stdout    

# assing Redirect with widget Text 
sys.stdout = Redirect(text)

root.mainloop()

# assign back original stdout (if you need it)
sys.stdout = old_stdout

But os.system you will have to replace with ie. subprocess.run() to catch output and print() it.

def test():
    print("Hello World")
    p = subprocess.run("ping -c 4 stackoverflow.com", shell=True, stdout=subprocess.PIPE)
    print(p.stdout.decode())

Minimal working code

import tkinter as tk
import os
import sys
import subprocess

# --- functions ---

def test():
    print("Hello World")
    p = subprocess.run("ping -c 4 stackoverflow.com", shell=True, stdout=subprocess.PIPE)
    print(p.stdout.decode())
    
# --- classes ---

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

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

    #def flush(self):
    #    pass
    
# --- main ---    
   
root = tk.Tk()

text = tk.Text(root)
text.pack()

button = tk.Button(root, text='TEST', command=test)
button.pack()

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

root.mainloop()

sys.stdout = old_stdout

Two problems:

It sends text from subprocess to widget Text after getting all text from ping. It may need more work to display line by line.

test() needs some time to finish work and it blocks tkinter so it can't update widgets and it freezes. It may need to run test() in separated thread but I don't know if this will not gives other problems because in many GUI frameworks you can't use widgets in separated threat.


EDIT:

Version with threading. It resolvs previous problems.

But it may have new problems :)

  • you can click button two times and it will run two pings at the same time and mix strings from both pings

  • code doesn't have method to stop thread (ie. when you run ping without -c 4)

Code:

import tkinter as tk
import sys
import subprocess
import threading 

# --- functions ---

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

def test():
    print("Hello World")

    p = subprocess.Popen("ping -c 4 stackoverflow.com".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)

    print("Finished")
             
# --- classes ---

class Redirect():

    def __init__(self, widget):
        self.widget = widget

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

    #def flush(self):
    #    pass

# --- main ---    

root = tk.Tk()

text = tk.Text(root)
text.pack()

button = tk.Button(root, text='TEST', command=run)
button.pack()

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

root.mainloop()

sys.stdout = old_stdout

EDIT: 2021.08.19

Version with Scrollbar and autoscroll

import tkinter as tk
import sys
import subprocess
import threading 

# --- classes ---

class Redirect():

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

    def write(self, text):
        self.widget.insert('end', text)
        if self.autoscroll:
            self.widget.see("end")  # autoscroll
        
    #def flush(self):
    #    pass

# --- functions ---

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

def test():
    print("Thread: start")

    p = subprocess.Popen("ping -c 4 stackoverflow.com".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)

    print("Thread: end")

# --- main ---    

root = tk.Tk()

# - Frame with Text and Scrollbar -

frame = tk.Frame(root)
frame.pack(expand=True, fill='both')

text = tk.Text(frame)
text.pack(side='left', fill='both', expand=True)

scrollbar = tk.Scrollbar(frame)
scrollbar.pack(side='right', fill='y')

text['yscrollcommand'] = scrollbar.set
scrollbar['command'] = text.yview

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

# - rest -

button = tk.Button(root, text='TEST', command=run)
button.pack()

root.mainloop()

# - after close window -

sys.stdout = old_stdout
Sign up to request clarification or add additional context in comments.

11 Comments

thanks for the effort, this definitely addresses my issue, although not to the fullest, as you mentioned, it freezes the app and when the wait time is longer wont be viable, ill try to find ways to fix that
link to Redirecting stdout to tkinter immediately (without waiting for the process to complete) in @acw1668 comment seems useful to resolve this problem
@pippo1980 my code doesn't use self.widget.see("end") but it should be in write(). I added it in answer. Did you run exactly my code to test if it works with your system?
if your code needs flush then uncomment def flush(self): pass
got it added stderr=subprocess.STDOUT to subprocess.run(aa ...) so subprocess.run(aa ..., stderr=subprocess.STDOUT)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.