3

I want to create a box were the user is informed of what the application is actually doing. I created a Text Widget were to show the print statements that I wrote in key points of the applications, so that it could serve as a log box.

To do this, I redirected the stdout to a subclass of the widget itself "upgraded" with a write method as I saw here in another post. This does indeed work, but I noticed a problem that makes the box almost useless.

If you run the code, you can see that the sentences appear all at once. More puzzling for me is that not only the sentences of the "wait2" functions appear togheter, but even the print statement of the calling function, "wait1", is shown at the end of the process.

Why this behaviour? what can I do to see the statement shown in the box as they are executed?

    from Tkinter import *
    import sys
    import time

    root = Tk()

    class addwritemethod(object): 
        def __init__(self, widget):
            self.widget = widget

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

    def  wait1():
        print "Can you see me?"
        wait2()

    def  wait2():
        for i in range(10):
            time.sleep(5)
            print "Long time no see!"


    t_go = Button(root, text= "Start!", width =12, command = wait1) 
    t_go.pack(side = LEFT, padx = 20, pady = 20)


    tlog = Text(root,wrap = "word")
    tlog.pack(side="top", fill="both", expand=True)
    tlog.configure(state="disabled")

    sys.stdout = addwritemethod(tlog)

    mainloop()

EDIT: I want to say thanks to the people who answered me and an apology: I did not give all the required information. I put time.sleep() in the test code only to show you the behaviour. In the real application, I trasfer a file via ssh with Paramiko and I don't use sleep(). Maybe I choose the wrong example, but the result is the same, all the print stament are shown at the same moment.

2
  • time.sleep() does not play well with several things, including tkinter. For delays in tkinter, use after(). Commented May 15, 2015 at 18:46
  • I'm sorry, I did not explain correctly that this was only a test to show that the print statement are printed all at once. In the real appication I don't use sleep() but Paramiko. Commented May 15, 2015 at 18:59

2 Answers 2

4

You could also use the built-in logging module to achieve your goal of

creating a box where the user is informed of what the application is actually doing ... a log box.

I had this same need and converged on the recommendations provided here and here.

I have an example below that I created to illustrate the concept of logging to a GUI control using Tkinter. The example below logs to a text control as you ask, but you can send log messages to other GUI components by replacing/copying the class MyHandlerText with other handler classes like MyHandlerLabel, MyHandlerListbox, etc. (choose your own names for the handler classes). Then you'd have a handler for a variety of GUI controls of interest. The big "a-ha" moment for me was the module-level getLogger concept encouraged by python.org.

import Tkinter
import logging
import datetime

# this item "module_logger" is visible only in this module,
# (but you can create references to the same logger object from other modules 
# by calling getLogger with an argument equal to the name of this module)
# this way, you can share or isolate loggers as desired across modules and across threads
# ...so it is module-level logging and it takes the name of this module (by using __name__)
# recommended per https://docs.python.org/2/library/logging.html
module_logger = logging.getLogger(__name__)

class simpleapp_tk(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent

        self.grid()

        self.mybutton = Tkinter.Button(self, text="ClickMe")
        self.mybutton.grid(column=0,row=0,sticky='EW')
        self.mybutton.bind("<ButtonRelease-1>", self.button_callback)

        self.mytext = Tkinter.Text(self, state="disabled")
        self.mytext.grid(column=0, row=1)

    def button_callback(self, event):
        now = datetime.datetime.now()
        module_logger.info(now)

class MyHandlerText(logging.StreamHandler):
    def __init__(self, textctrl):
        logging.StreamHandler.__init__(self) # initialize parent
        self.textctrl = textctrl

    def emit(self, record):
        msg = self.format(record)
        self.textctrl.config(state="normal")
        self.textctrl.insert("end", msg + "\n")
        self.flush()
        self.textctrl.config(state="disabled")

if __name__ == "__main__":

    # create Tk object instance
    app = simpleapp_tk(None)
    app.title('my application')

    # setup logging handlers using the Tk instance created above
    # the pattern below can be used in other threads...
    # ...to allow other thread to send msgs to the gui
    # in this example, we set up two handlers just for demonstration (you could add a fileHandler, etc)
    stderrHandler = logging.StreamHandler()  # no arguments => stderr
    module_logger.addHandler(stderrHandler)
    guiHandler = MyHandlerText(app.mytext)
    module_logger.addHandler(guiHandler)
    module_logger.setLevel(logging.INFO)
    module_logger.info("from main")    

    # start Tk
    app.mainloop()
Sign up to request clarification or add additional context in comments.

Comments

1

When you call sleep, the application does exactly that: it sleeps. When it's sleeping it can't update the display. As a general rule you should never call sleep in a GUI.

That being said, a quick fix is to make sure you call update after printing something to the log, so that Tkinter has a chance to update the screen. Add self.widget.update_idletasks() at the end of write (redrawing the screen is considered an "idle task").

This isn't a proper fix but it's good enough to illustrate why the data isn't appearing. A proper fix involves not calling sleep. There are many examples on stackoverflow related to this, and almost all of them involve using the after method.

2 Comments

Thanks for the answer. I edited the original post to poitn out tht I don't use sleep() in the real code. However, why once the sleep time ends (and the app "wakes up" it doesn't write a single line? I would expect that every 5 seconds a line would show up, but that are shown all at once.
@Lornioiz: when you call update, all pending redraw events will be processed. If you've written several items without calling update, they will all appear at the same time. If you've called a long-running command, nothing at all will happen while that command runs.

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.