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')