1

I am using Tkinter to make a GUI that runs two other python scripts that I have. I wanted to print out the console to the window. I did this initially with a Label widget, and it did update and show the console as new lines were created.

Problems arose, however, when I realized that I couldn't scroll with that. The text kept making too many lines to display within the window. I searched online and found that there was a Text widget in Tkinter, but the problem I have now is that the Text widget does not update regardless if I do myText.update(), root.update(), or both.

Once again, it updated in real time with the Label widget, but now that its the Text widget it doesn't update until after the program has finished running. This is of course an issue if I want to give the users of my program a live feed of what the current process is doing.

Here is my code for reference:

with subprocess.Popen(['python.exe','Test2.py'], stdout=subprocess.PIPE,
                      stderr=subprocess.STDOUT) as process:
    for line in process.stdout:
        text= line.decode('utf8')
        myText.insert(END, text)
        myText.update()

Edit: For some added context here is the rest of my program (with some edits):

from tkinter import *
import subprocess
import threading as thread

root = Tk()
root.geometry('1280x720')
root.title("Run Other Programs GUI")
frame = Frame(root, width=1280).grid(row=0, column=2)
frame2 = Frame(root, width=1280).grid(row=1,column=2)
#Creating Labels
myLabel1 = Label(frame, text="Main Program").grid(row=0, column=2, sticky='')
myLabel2 = Label(frame, text="Side Program").grid(row=3, column=2, sticky='')
myText = Text(frame2)
myText.grid(row=6, column=2)
myLabel3 = Label(frame2, text='')
myLabel3.grid(row=7, column=2, sticky='')

def getText(line):
    text = line.decode('utf8')
    return text
def insertText(myText, text):
    myText.insert(END, text)


def appear(index):
    # Disable the button by index
    global myLabel3
    myLabel3.config(text="")
    buttons[index].config(text="Running Program...")
    buttons[0].config(state="disabled")
    buttons[1].config(state="disabled")
    root.update()
    if(index==0):
        with subprocess.Popen(['python.exe','Test2.py'], stdout=subprocess.PIPE,
                      stderr=subprocess.STDOUT) as process:
            for line in process.stdout:
                text= line.decode('utf8')
                myText.insert(END, text)
                myText.update()
        buttons[index].config(state="active")
        buttons[index].config(text="Click to run program")
        buttons[1].config(state="active")
        myLabel3.config(text="Finished Running Main Program!")
    elif(index==1): #need to work on this still
        subprocess.call(['python.exe',"Test1.py"])
        buttons[index].config(state="active")
        buttons[index].config(text="Click to run program")
        buttons[0].config(state="active")
        myLabel3.config(text="Finished Running Side Program!")
    else:
        print("Error, this shouldn't be happening, button index is not defined")

# A collection (list) to hold the references to the buttons created below
buttons = []

for index in range(2): 

    button = Button(frame, bg="White", text="Click to run program", width=50, height=3, relief=GROOVE,
                    command=lambda index=index: appear(index))

    # Add the button to the window
    button.grid(padx=2, pady=2, row=(index+1)*2, column=2, sticky='')

    # Add a reference to the button to 'buttons'
    buttons.append(button)

root.mainloop()
acw1668
  • 40,144
  • 5
  • 22
  • 34
  • You will likely need to use async or multithreading to fix this. Your script is blocking all updates and trying to update within your script is probably not going to work either. – OneMadGypsy Jul 21 '22 at 15:23
  • You could pipe the output into a separate process that read from its stdin and displayed it in a scrollable tkinter `Text` widget. This is essentially what the code in this [answer](https://stackoverflow.com/a/49016673/355230) of mine does for both stdout and stderr. – martineau Jul 21 '22 at 15:44
  • I haven't tried it, but the technique shown in this article using `process.call()` looks promising: [Getting live output from subprocess using poll](https://fabianlee.org/2019/09/15/python-getting-live-output-from-subprocess-using-poll/). Polling is one way to avoid interfering with tkinter's `mainloop()`. – martineau Jul 21 '22 at 15:50
  • For the first two comments, I don't understand async, multithreading, or multiprocessing conceptually so those answers aren't working for me. I tried the third comment by @martineau, as that one was the least confusing for me to implement, however it gives me the same result. Nothing updates until the subprocess is finished running. – TheLegendOfLame Jul 21 '22 at 16:24
  • I had issues too with the code from the 3rd comment and I found https://stackoverflow.com/questions/72101156/how-to-get-live-output-with-subprocess-in-python helpful. I had to add `flush=True` in the script, in your case `Test2.py`. – j_4321 Jul 21 '22 at 17:34
  • @j_4321 I tried adding flush but for some reason it appears to do nothing for me. I have no clue why. I tried looking it up to no avail, as all solutions for flush not doing anything, that I've seen, don't seem to help at all. – TheLegendOfLame Jul 21 '22 at 18:36
  • Try `['python.exe', '-u', 'Test2.py']` instead. – acw1668 Jul 22 '22 at 09:42
  • @acw1668 That works perfectly! Just what I needed and super easy to implement! – TheLegendOfLame Jul 22 '22 at 13:21

1 Answers1

1

@acw1668's comment was the solution that worked for me. For reference:

with subprocess.Popen(['python.exe', '-u', 'Test2.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process:
        for line in process.stdout:
            line = line.decode() # defaulting to system encoding
            myText.insert(END, line)
            myText.update()
            process.poll()