5

I am trying to redirect stdout to a Label widget. The goal is to "print" into the Label all the Python prints that are in my script.

But when I click on BUTTON1 nothing happens...

Here is my code:

from Tkinter import *
import sys
import tkMessageBox


class App:

    def __init__(self, master):

        self.frame = Frame(master, borderwidth=5, relief=RIDGE)
        self.frame.grid()

        class IORedirector(object):
            def __init__(self,TEXT_INFO):
                self.TEXT_INFO = TEXT_INFO

        class StdoutRedirector(IORedirector):
            def write(self,str):
                self.TEXT_INFO.config(text=str)

        self.TEXT_HEADER = self.text_intro = Label(self.frame, bg="lightblue",text="MY SUPER PROGRAMM") ## HEADER TEXT
        self.TEXT_HEADER.grid(row=0, column=0, columnspan=2, sticky=W+E+N+S)

        self.MENU = Frame(self.frame, borderwidth=5, relief=RIDGE, height=12) 

        self.MENU.grid(row=1, column=0, sticky=N)

        self.button = Button(self.MENU, text="QUIT", fg="red", bg="red", command=self.frame.quit)
        self.button.grid(row=4, column=0)

        self.BUTTON1 = Button(self.MENU, text="BUTTON1", command=self.BUTTON1_CMD)
        self.BUTTON1.grid(row=0, column=0,sticky=W+E)

        self.TEXT_INFO = Label(self.frame, height=12, width=40, text="I WANT TO SEE THE STDOUT OUTPUT HERE", bg="grey",borderwidth=5, relief=RIDGE)
        self.TEXT_INFO.grid(row=1, column=1)

        sys.stdout = StdoutRedirector(self.TEXT_INFO)

    def BUTTON1_CMD(self):
        print "TEST NUMBER ONE"
        print "TEST NUMBER TWO"


root = Tk()
app = App(root)
root.mainloop()
nbro
  • 15,395
  • 32
  • 113
  • 196

2 Answers2

4

The reason you are not seeing the text set is that it is set correctly for a split second and then immediately set to blank. This is because print is sending a newline to stdout after the print statements. Here is a modified version that appends to the Label rather than overwrite it for every print statement.

    class StdoutRedirector(IORedirector):
        def write(self,str):
           self.TEXT_INFO.config(text=self.TEXT_INFO.cget('text') + str)
Jesse Harris
  • 1,131
  • 6
  • 10
  • 1
    You're awesome ! This is working perfectly ! Thank you for your help, I didn't know that print is always sending a newline to stdout after the print statement, good to know ;-) (I can't vote up 'cause I don't have more than 15 reputation but as soon as I have it, I will vote for you ;) ) –  Jun 01 '12 at 09:07
3

I made a class which copies stdout write calls to a tkinter widget be it a Label or a Text. Works for me on Python3.3.1/WindowsXp.:

import sys

class StdoutToWidget:
    '''
    Retrieves sys.stdout and show write calls also in a tkinter
    widget. It accepts widgets which have a "text" config and defines
    their width and height in characters. It also accepts Text widgets.
    Use stop() to stop retrieving.

    You can manage output height by using the keyword argument. By default
    the class tries to get widget\'s height configuration and use that. If
    that fails it sets self.height to None which you can also do manually.
    In this case the output will not be trimmed. However if you do not
    manage your widget, it can grow vertically hard by getting more and
    more inputs.
    '''

    # Inspired by Jesse Harris and Stathis
    # http://stackoverflow.com/a/10846997/2334951
    # http://stackoverflow.com/q/14710529/2334951

    # TODO: horizontal wrapping
    #       make it a widget decorator (if possible)
    #       height management for Text widget mode

    def __init__(self, widget, height='default', width='default'):
        self._content = []
        self.defstdout = sys.stdout
        self.widget = widget

        if height == 'default':
            try:
                self.height = widget.cget('height')
            except:
                self.height = None
        else:
            self.height = height
        if width == 'default':
            try:
                self.width = widget.cget('width')
            except:
                self.width = None
        else:
            self.width = width   

    def flush(self):
        '''
        Frame sys.stdout's flush method.
        '''
        self.defstdout.flush()

    def write(self, string, end=None):
        '''
        Frame sys.stdout's write method. This method puts the input
        strings to the widget.
        '''

        if string is not None:
            self.defstdout.write(string)
            try:
                last_line_last_char = self._content[-1][-1]
            except IndexError:
                last_line_last_char = '\n'
            else:
                if last_line_last_char == '\n':
                    self._content[-1] = self._content[-1][:-1]

            if last_line_last_char != '\n' and string.startswith('\r'):
                self._content[-1] = string[1:]
            elif last_line_last_char != '\n':
                self._content[-1] += string
            elif last_line_last_char == '\n' and string.startswith('\r'):
                self._content.append(string[1:])
            else:
                self._content.append(string)

        if hasattr(self.widget, 'insert') and hasattr(self.widget, 'see'):
            self._write_to_textwidget()
        else:
            self._write_to_regularwidget(end)

    def _write_to_regularwidget(self, end):
        if self.height is None:
            self.widget.config(text='\n'.join(self.content))
        else:
            if not end:
                content = '\n'.join(self.content[-self.height:])
            else:
                content = '\n'.join(self.content[-self.height+end:end])
            self.widget.config(text=content)

    def _write_to_textwidget(self):
        self.widget.insert('end', '\n'.join(self.content))
        self.widget.see('end')        

    def start(self):
        '''
        Starts retrieving.
        '''
        sys.stdout = self

    def stop(self):
        '''
        Stops retrieving.
        '''
        sys.stdout = self.defstdout

    @property
    def content(self):
        c = []
        for li in self._content:
            c.extend(li.split('\n'))

        if not self.width:
            return c
        else:
            result = []
            for li in c:
                while len(li) > self.width:
                    result.append(li[:self.width])
                    li = li[self.width:]
                result.append(li)
            return result

    @content.setter
    def content(self, string):
        self._content = string.split('\n')

    @property
    def errors(self):
        return self.defstdout.errors

    @property
    def encoding(self):
        return self.defstdout.encoding

EDIT1: I received a downvote, so here is the updated one. I use this in a Label widget and print() functions appear smoothly in my widget. Moreover as an extra feature if I pass None to the write call and let's say -1 as end argument, then it won't show last line (careful with indexing). I use this because I attached a slider to the widget. I will publish a demo soon.

SzieberthAdam
  • 3,999
  • 2
  • 23
  • 31