5

I am trying to redirect stdout into a Tkinter Text widget in real time using Python 2.7.2 on Windows 7. In my code below, I intended to have a print statement appear in the Text widget every 2 seconds. What happens, instead, is that all 3 lines

Received sleep-command
Going to sleep for 2 s
Just woke up

appear simultaneously as soon as both sleep() and mod1.main() have been evaluated.

Surprisingly (to me, at least) the problem disappears when I do not redirect stdout. That is, if I comment out the line

sys.stdout = StdoutRedirector(outputPanel)

IDLE displays the print statements in real time. Can somebody explain this to me and suggest a way to get around it? Thanks in advance!

Here is my example code:

mod1.py

import time
import sys

def main():
    print('Going to sleep for 2 s')
    time.sleep(2)
    print('Just woke up')

text_GUI.py

from Tkinter import *
import sys
import time
import mod1

old_stdout = sys.stdout

class StdoutRedirector(object):

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

    def write(self, str):
        self.text_area.insert(END, str)
        self.text_area.see(END)

def sleep():
    print('Received sleep-command')
    time.sleep(2)
    mod1.main()

root = Tk()

# Textbox
outputPanel = Text(root, wrap='word', height = 11, width=50)
outputPanel.grid(column=0, row=0, columnspan = 2, sticky='NSWE', padx=5, pady=5)
sys.stdout = StdoutRedirector(outputPanel)

# Sleep button
sleepBtn = Button(root, text='Sleep', command=sleep)
sleepBtn.grid(row=1, column=1, sticky='E', padx=5, pady=5)

root.mainloop()

sys.stdout = old_stdout
Stathis
  • 53
  • 1
  • 5
  • Have you tried calling `.flush()`? – Wooble Feb 05 '13 at 15:11
  • I don't see the point with IORedirector at all. Why not just have a __init__ inside StdoutRedirector? – Torxed Feb 05 '13 at 15:22
  • 1
    @Torxed: This is how I found it in some other post and took it as is. But you are right. It is superfluous. I will edit it accordingly. Thanks! – Stathis Feb 05 '13 at 15:57
  • @Wooble: Since I am redirecting stdout into a Text Widget, I cannot use .flush() because the Widget does not have a 'flush' attribute. Is there some other way I could do that? – Stathis Feb 05 '13 at 16:04
  • I made a class which does this: http://stackoverflow.com/a/16460878/2334951 – SzieberthAdam May 09 '13 at 12:05

3 Answers3

6

Call sleepBtn.update_idletasks() before each time.sleep(2) command. Otherwise the view will not be updated before the end of the sleep procedure.

Michael Westwort
  • 914
  • 12
  • 24
  • And don't forget to call sys.stdout.flush() periodicly before rendering any data, otherwise you'd might miss buffered in stdout. – Torxed Feb 05 '13 at 15:22
  • That was it. Thank you very much for your help! Unfortunately, as this was my first contribution in stackoverflow, I do not have upvoting rights. – Stathis Feb 05 '13 at 15:48
  • @Torxed: Please see my comment at Wobble regarding .flush(). If there is a way of getting around the lack of 'flush' attribute issue, please let me know. – Stathis Feb 05 '13 at 16:10
  • @Stathis Humm that's true, perhaps you don't need to flush because there isn't even a buffert to begin with in your own class, it just pipes directly to the textobject which, is what you were looking for. The only downside to this approach is that you'd might get screen-tear (incomplete text being shown) but with a descent refreshrate that's not even an issue worth mentioning. So, for all intense and purposes, you've succeeded :) – Torxed Feb 05 '13 at 16:15
  • 1
    the `flush` of your redirector should refresh the screen (i.e. call `self.text_area.update_idletasks()`) so that if your program tries to manually flush the file it insures the screen is updated. – Tadhg McDonald-Jensen Mar 08 '16 at 19:19
0

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.:

https://stackoverflow.com/a/16460878/2334951

Community
  • 1
  • 1
SzieberthAdam
  • 3,999
  • 2
  • 23
  • 31
0

Instead of sleepBtn.update_idletasks(), this may be a neater solution:

Put self.text_area.update_idletasks() before self.text_area.insert(END, str)

Jason
  • 2,950
  • 2
  • 30
  • 50