6

My question is almost the same as this one: Widget to Display subprocess stdout? but a step further.

I have the following code (python2.7):

def btnGoClick(p1):
    params = w.line.get()
    if len(params) == 0:
        return

    # create child window
    win = tk.Toplevel()
    win.title('Bash')
    win.resizable(0, 0)
    # create a frame to be able to attach the scrollbar
    frame = ttk.Frame(win)
    # the Text widget - the size of the widget define the size of the window
    t = tk.Text(frame, width=80, bg="black", fg="green")
    t.pack(side="left", fill="both")
    s = ttk.Scrollbar(frame)
    s.pack(side="right", fill="y")
    # link the text and scrollbar widgets
    s.config(command=t.yview)
    t.config(yscrollcommand=s.set)
    frame.pack()

    process = subprocess.Popen(["<bashscript>", params], shell=False,
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    while True:
        out = process.stdout.readline()
        if out == '' and process.poll() is not None:
            break
        print out
        t.insert(tk.END, out)

The output from the "longrunning" bash script is captured in realtime (appear in the console) but the Tkinter window appear only after the end of the subprocess !!

How can I have the window appearing before the subprocess start and update its content in realtime ?

Community
  • 1
  • 1
ericc
  • 741
  • 3
  • 8
  • 19
  • 1
    to avoid blocking the GUI, you could put `readline()` into separate thread, [example](https://gist.github.com/zed/42324397516310c86288) – jfs Jun 01 '13 at 06:11
  • if you are on a POSIXy system then you could also [use `createfilehandler()` to get output from the subprocess in "real-time"](https://gist.github.com/zed/9294978) instead of the explicit threads. – jfs Dec 29 '14 at 01:44
  • related: [Redirect command line results to a tkinter GUI](http://stackoverflow.com/q/665566/4279) – jfs Sep 20 '15 at 18:04

3 Answers3

4

Finally I found the solution. After the window construction, you must add :

frame.pack()
# force drawing of the window
win.update_idletasks()

And then after every line insertion in the widget, you must also force a refresh with the same method only on the widget.

# insert the line in the Text widget
t.insert(tk.END, out)
# force widget to display the end of the text (follow the input)
t.see(tk.END)
# force refresh of the widget to be sure that thing are displayed
t.update_idletasks()
ericc
  • 741
  • 3
  • 8
  • 19
  • 1
    as suspected, I tried and this example is not really work. If as command you use something like "ls -Rf /" you will see that the while loop will make the window txt output flowing pretty well. However both windows (main and secondary) will block big time. – Fabrizio May 30 '13 at 13:08
  • yes, it's correct. During the output, everything is blocked and it's just an output. you can't interact with it. But this is what I need. – ericc May 31 '13 at 17:40
  • Mate, it can't possibly be that you want this behaviour. The usability of the GUI is compromised by this behaviour. Resize, close button, minimize button is all frozen. – Fabrizio Jun 03 '13 at 13:32
  • well, this is not really a big deal, as the purpose of the interface is to launch this subprocess which doesn't take, anyway, not so much time. I will try to implement the solution proposed by J.F. Sebastian, to see if thing can be improved – ericc Jun 04 '13 at 15:59
1

This is an interesting solution. Would that be possible to have the whole working code?

I am asking because I was wonder how the while True does not block the usability of the whole GUI... does it not?

As suspected, I tried and this example is not really work. If you use something like "ls -Rf /" as command, you will see that the while loop will make the txt output flowing pretty well. However both windows (main and secondary) will block big time.

I suspect you need to send the print txt part in a separated thread or process. Or, if you use pygtk you can use stuff like

gobject.io_add_watch(self.ep1.stdout,       # file descriptor
                     gobject.IO_IN,         # condition
                     self.write_to_buffer ) # callback

which was actually invented for this.

mkj
  • 2,761
  • 5
  • 24
  • 28
Fabrizio
  • 437
  • 3
  • 14
  • here's two examples: [one uses `io_add_watch()`, another -- threads](https://gist.github.com/zed/8a255e81eb87431c0e63) – jfs Jun 01 '13 at 06:17
1

Just in case someone else is looking for it...

log_box_1 = tk.Text(root, borderwidth=3, relief="sunken")

with subprocess.Popen("ls -la", shell=True, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as p:
            for line in p.stdout:
                log_box_1.insert(tk.END, line)

From here

Lucas
  • 1,514
  • 3
  • 16
  • 23