0

I'm a little stuck here. I've read lots of stack overflows threads but not getting any further on that topic.

My goal is to have a tinter GUI that at some point launches a function in a new Process and redirects every print in that function to the Guis Text widget. There a Pipes and Queues but im not to familiar with how to use them correctly. I have found a working solution here but that only applies to Python 3. Unfortunately I have to use Python 2.7...

Can anybody help?

my sample code:

from Tkinter import *
import multiprocessing as mp
import time
import sys

class Gui(object):

    def __init__(self):
        self.a=Tk()
        b1=Button(self.a, text="Process 1", command=self.func)
        b1.grid(row=0, column=0, pady=10, padx=10, sticky=SE)

        self.messages=Text(
            self.a, height=2.5, width=30, bg="light cyan", state=NORMAL)
        self.messages.grid(row=1, column=0, columnspan=3)


        sys.stdout = self.StdoutRedirector(self.messages)
        sys.stderr = self.StdoutRedirector(self.messages)
        
        self.a.mainloop()
        
    class StdoutRedirector(object):
        def __init__(self, text_widget):
            self.output = text_widget

        def write(self, string):
            self.output.config(state=NORMAL)
            self.output.update_idletasks()
            self.output.insert('end', string)
            self.output.see('end')
            self.output.config(state=DISABLED)

        def flush(self):
            pass

    def func(self):
        print("test")
        proc=mp.Process(target=go)
        proc.start()


def go():
    for i in range(0,10):
        time.sleep((1))
        print(i)

if __name__ == "__main__":
    Gui()
acw1668
  • 40,144
  • 5
  • 22
  • 34
Kann Nix
  • 13
  • 2

1 Answers1

0

The issue in your code is that you are changing the content of a tkinter widget outside of the mainloop, which is not possible. One solution I have found is to redirect the stdout and stderr to Queues and periodically read the content of these queues in the mainloop to update the text widget. The periodical reading is done using the method .after(<delay in ms>, <callback>).

from Tkinter import *
import multiprocessing as mp
import time
import sys

class StdoutRedirector(object):
    def __init__(self, queue):
        self.output = queue

    def write(self, string):
        self.output.put(string)

    def flush(self):
        pass

class Gui(object):

    def __init__(self):
        self.a=Tk()
        b1 = Button(self.a, text="Process 1", command=self.func)
        b1.grid(row=0, column=0, pady=10, padx=10, sticky=SE)

        self.messages=Text(self.a, height=2.5, width=30, bg="light cyan",
                           state=NORMAL)
        self.messages.grid(row=1, column=0, columnspan=3)

        self.messages.tag_configure('error', foreground='red')
        # queues to get the stdout and stderr
        self.stdout = mp.Queue()
        self.stderr = mp.Queue()
        self.proc = None  # process
        self.a.mainloop()

    def stdout_update(self):
        self.messages.configure(state=NORMAL)
        while not self.stdout.empty():  # display stdout
            self.messages.insert('end', self.stdout.get(False))
        while not self.stderr.empty():  # display stderr
            self.messages.insert('end', self.stderr.get(False), 'error')
        self.messages.configure(state=DISABLED)
        if self.proc.is_alive(): # the process is not finished, schedule the next display update
            self.a.after(100, self.stdout_update)

    def func(self):
        self.proc = mp.Process(target=go, args=(self.stdout, self.stderr))
        self.proc.start()
        self.stdout_update()  # launch display update


def go(queue_out, queue_err):
    # redirect stdout and stderr to queues
    sys.stdout = StdoutRedirector(queue_out)
    sys.stderr = StdoutRedirector(queue_err)
    for i in range(0, 2):
        time.sleep(1)
        print(i)
    1/0 # to test the stderr

if __name__ == "__main__":
    Gui()
j_4321
  • 15,431
  • 3
  • 34
  • 61