3

I'm trying to redirect the stdout of a function to a tkinter text widget. The problem I am running into is that it writes each line to a new window instead of listing everything in one. The function scans a directory and lists any file that is 0k. If no files are 0k it prints that. So, the problem is that if there are 30 0k files in a directory, it will open 30 windows with a single line in each. Now, I know what the problem is. If you look in my function code Zerok() I am telling it:

if os.stat(filename).st_size==0:  
       redirector(filename)

I know that every time os.stat sees a file that is 0k it is then sending that to redirector, that's why its a new window for each file. I just have no idea how to fix it. Complete code below. Thanks for the help.

import Tkinter
from Tkinter import *
import tkFileDialog

class IORedirector(object):
    '''A general class for redirecting I/O to this Text widget.'''
    def __init__(self,text_area):
        self.text_area = text_area

class StdoutRedirector(IORedirector):
    '''A class for redirecting stdout to this Text widget.'''
    def write(self,str):
        self.text_area.write(str,False)

def redirector(inputStr):
    import sys
    root = Tk()
    sys.stdout = StdoutRedirector(root)
    T = Text(root)
    T.pack()
    T.insert(END, inputStr)

####This Function checks a User defined directory for 0k files
def Zerok():
    import os
    sys.stdout.write = redirector #whenever sys.stdout.write is called, redirector is called.
    PATH = tkFileDialog.askdirectory(initialdir="/",title='Please select a directory')  
    for root,dirs,files in os.walk(PATH):  
     for name in files:  
      filename=os.path.join(root,name)  
      if os.stat(filename).st_size==0:  
       redirector(filename)
      else:
          redirector("There are no empty files in that Directory")
          break

#############################Main GUI Window###########################
win = Tk()
f = Frame(win)
b1 = Button(f,text="List Size")
b2 = Button(f,text="ZeroK")
b3 = Button(f,text="Rename")
b4 = Button(f,text="ListGen")
b5 = Button(f,text="ListDir")
b1.pack()
b2.pack()
b3.pack()
b4.pack()
b5.pack()
l = Label(win, text="Select an Option")
l.pack()
f.pack()
b2.configure(command=Zerok)
win.mainloop()
1
  • On a side note, use import tkinter.filedialog it is built into the standard library Commented Oct 5, 2021 at 4:56

2 Answers 2

3

The fix is simple: don't create more than one redirector. The whole point of the redirector is that you create it once, and then normal print statements will show up in that window.

You'll need to make a couple of small changes to your redirector function. First, it shouldn't call Tk; instead, it should create an instance of Toplevel since a tkinter program must have exactly one root window. Second, you must pass a text widget to IORedirector since it needs to know the exact widget to write to.

def redirector(inputStr=""):
    import sys
    root = Toplevel()
    T = Text(root)
    sys.stdout = StdoutRedirector(T)
    T.pack()
    T.insert(END, inputStr)

Next, you should only call this function a single time. From then on, to have data appear in the window you would use a normal print statement.

You can create it in the main block of code:

win = Tk()
...
r = redirector()
win.mainloop()

Next, you need to modify the write function, since it must write to the text widget:

class StdoutRedirector(IORedirector):
    '''A class for redirecting stdout to this Text widget.'''
    def write(self,str):
        self.text_area.insert("end", str)

Finally, change your Zerok function to use print statements:

def Zerok(): ... if os.stat(filename).st_size==0:
print(filename) else: print("There are no empty files in that Directory") break

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks Bryan, most of this makes sense but I'm lost on a few points.
Thanks @Bryan Oakley, I'm super new to python and even newer to Tkinter so your help and patience is appreciated. Most of this makes sense but I'm lost on a few points. First, "you must pass a Text widget to IORedirector..." I'm lost there, can you clarify? Second, calling this function a single time and r = redirector(). I'm not getting how that works and is used?
@Dennis: I edited my answer to include a piece I originally forgot to add -- the write method needs to use the function for writing to the text file. As for passing a text widget -- the whole point is for the redirector to redirect to something. You have to tell it what that something is. In this case you want it redirected to the text widget.
Thank you, I'm starting to understand. The only thing I'm still unclear on is "Second, you must pass a text widget to IORedirector since it needs to know the exact widget to write to." I completely understand the Why I just dont understand the How. Can you clarify in the code? ' class IORedirector(object): '''A general class for redirecting I/O to this Text widget.''' def __init__(self,text_area): self.text_area = text_area
3

The above solution is very complete; I was able to essentially copy and paste it into my code with only one small modification. I'm not entirely sure why, but the StdoutRedirector requires a flush method.

I'm guessing it's because sys.stdout calls a flush() method when it exits, but I'm haven't waded into the docs deep enough to actually understand what that means.

Running the above code in the Jupyter environment caused the code to hang indefinitely until the kernel was restarted. The console kicks the following errors:

sys.stdout.flush()
AttributeError: 'StdoutRedirector' object has no attribute 'flush'
ERROR:tornado.general:Uncaught exception, closing connection.

The simple solution is to make a small change to the StdoutRedirector class by adding a flush method.

class StdoutRedirector(IORedirector):
    '''A class for redirecting stdout to this Text widget.'''

    def write(self,str):
        self.text_area.insert("end", str)
    def flush(self):
        pass

Thanks to the giants that came before me and offered this very clear explanation.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.