How to redirect print statements to Tkinter text widget

I have a Python program which performs a set of operations and prints the response on STDOUT. Now I am writing a GUI which will call that already existing code and I want to print the same contents in the GUI instead of STDOUT. I will be using the Text widget for this purpose. I do not want to modify my existing code which does the task (This code is used by some other programs as well).

Can someone please point me to how I can use this existing task definition and use its STDOUT result and insert it into a text widget? In the main GUI program I want to call this task definition and print its results to STDOUT. Is there a way to use this information?


Solution 1:

You can probably solve this by replacing sys.stdout with your own file-like object that writes to the text widget.

For example:

import Tkinter as tk
import sys

class ExampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        toolbar = tk.Frame(self)
        toolbar.pack(side="top", fill="x")
        b1 = tk.Button(self, text="print to stdout", command=self.print_stdout)
        b2 = tk.Button(self, text="print to stderr", command=self.print_stderr)
        b1.pack(in_=toolbar, side="left")
        b2.pack(in_=toolbar, side="left")
        self.text = tk.Text(self, wrap="word")
        self.text.pack(side="top", fill="both", expand=True)
        self.text.tag_configure("stderr", foreground="#b22222")

        sys.stdout = TextRedirector(self.text, "stdout")
        sys.stderr = TextRedirector(self.text, "stderr")

    def print_stdout(self):
        '''Illustrate that using 'print' writes to stdout'''
        print "this is stdout"

    def print_stderr(self):
        '''Illustrate that we can write directly to stderr'''
        sys.stderr.write("this is stderr\n")

class TextRedirector(object):
    def __init__(self, widget, tag="stdout"):
        self.widget = widget
        self.tag = tag

    def write(self, str):
        self.widget.configure(state="normal")
        self.widget.insert("end", str, (self.tag,))
        self.widget.configure(state="disabled")

app = ExampleApp()
app.mainloop()

Solution 2:

In python, whenever you call print('examplestring'), you're indirectly calling sys.stdout.write('examplestring') :

from tkinter import *
root=Tk()
textbox=Text(root)
textbox.pack()
button1=Button(root, text='output', command=lambda : print('printing to GUI'))
button1.pack()

Method 1: Print out on GUI

def redirector(inputStr):
    textbox.insert(INSERT, inputStr)

sys.stdout.write = redirector #whenever sys.stdout.write is called, redirector is called.

root.mainloop()

Infact we're calling print -(callsfor)-> sys.stdout.write -(callsfor)-> redirector

Method 2: Writing a decorator - print out on both CLI and GUI

def decorator(func):
    def inner(inputStr):
        try:
            textbox.insert(INSERT, inputStr)
            return func(inputStr)
        except:
            return func(inputStr)
    return inner

sys.stdout.write=decorator(sys.stdout.write)
#print=decorator(print)  #you can actually write this but not recommended

root.mainloop()

What a decorator does is it actually assign the func sys.stdout.write to func inner

sys.stdout.write=inner

and func inner adds an extra line of code before calling back the actual sys.stdout.write

This is in a way updating the older func sys.stdout.write to have new feature. You will notice that I used a try-except such that if there's any error in printing to the textbox, I would at least retain the original func of sys.stdout.write to the CLI

Method 3: Bryan Oakley's example

...
    sys.stdout = TextRedirector(self.text, "stdout")
...
class TextRedirector(object):
    def __init__(self, widget, tag="stdout"):
        self.widget = widget
        self.tag = tag

    def write(self, str):
        self.widget.configure(state="normal")
        self.widget.insert("end", str, (self.tag,))
        self.widget.configure(state="disabled")

What he did was that he assigned sys.stdout to Class TextRedirector with a Method .write(str)

so calling print('string') -calls for-> sys.stdout.write('string') -callsfor-> TextRedirector.write('string')