2009-03-20 20 views
15

He creado un programa que imprime los resultados en la línea de comandos. (Es el servidor e imprime el inicio de sesión en línea de comandos.)Redirigir los resultados de la línea de comando a una interfaz gráfica de usuario de tkinter

Ahora, quiero ver el mismo resultado para GUI.

¿Cómo puedo redirigir los resultados de la línea de comandos a la GUI?

Por favor, sugiera un truco para transformar fácilmente la aplicación de la consola a una simple GUI.

Tenga en cuenta que debería funcionar en Linux y Windows.

+0

relacionado: [Mostrar salida en tiempo real de un subproceso en un widget de tkinter] (http://stackoverflow.com/q/15362372/4279) – jfs

Respuesta

9

Puede crear un contenedor de scripts que ejecute su programa de línea de comandos como un subproceso, luego agregue el resultado a algo así como un widget de texto.

from tkinter import * 
import subprocess as sub 
p = sub.Popen('./script',stdout=sub.PIPE,stderr=sub.PIPE) 
output, errors = p.communicate() 

root = Tk() 
text = Text(root) 
text.pack() 
text.insert(END, output) 
root.mainloop() 

donde script es su programa. Obviamente puede imprimir los errores en un color diferente, o algo así.

+0

Lo probé en Linux, no funciona, por favor sugiera una solución .. –

+0

Falta un root.mainloop() al final para iniciar el ciclo de eventos. aparte de eso, parece que debería funcionar. – mavnn

+0

Sí, no tenía un sistema Linux para probarlo, así que todo era de memoria ... –

4

Redirigir la salida estándar a un método de escritura() que actualiza su interfaz gráfica es una forma de hacerlo, y probablemente la más rápida, aunque ejecutar un subproceso es probablemente una solución más elegante.

¡Solo redirige stderr una vez que estés realmente seguro de que está funcionando!

Ejemplo implimentation (archivo GUI y escritura de la prueba):

test_gui.py:

from Tkinter import * 
import sys 
sys.path.append("/path/to/script/file/directory/") 

class App(Frame): 
    def run_script(self): 
     sys.stdout = self 
     ## sys.stderr = self 
     try: 
      del(sys.modules["test_script"]) 
     except: 
      ## Yeah, it's a real ugly solution... 
      pass 
     import test_script 
     test_script.HelloWorld() 
     sys.stdout = sys.__stdout__ 
     ## sys.stderr = __stderr__ 

    def build_widgets(self): 
     self.text1 = Text(self) 
     self.text1.pack(side=TOP) 
     self.button = Button(self) 
     self.button["text"] = "Trigger script" 
     self.button["command"] = self.run_script 
     self.button.pack(side=TOP) 

    def write(self, txt): 
     self.text1.insert(INSERT, txt) 

    def __init__(self, master=None): 
     Frame.__init__(self, master) 
     self.pack() 
     self.build_widgets() 

root = Tk() 
app = App(master = root) 
app.mainloop() 

test_script.py:

print "Hello world!" 

def HelloWorld(): 
    print "HelloWorldFromDef!" 
+0

no redirige 'sys.stdout' en el nivel de descriptor de archivo, es decir, si llama a' os.write (1, b'not redirected ') 'en' test_script.py', entonces no lo verá en la GUI.Ver [Redireccionar stdout a un archivo en Python?] (Http://stackoverflow.com/a/22434262/4279) – jfs

7

Para mostrar subproceso de salida en una interfaz gráfica de usuario mientras todavía se está ejecutando, una solución portátil stdlib-only que funciona en Python 2 y 3 tiene que usar un hilo de fondo:

#!/usr/bin/python 
""" 
- read output from a subprocess in a background thread 
- show the output in the GUI 
""" 
import sys 
from itertools import islice 
from subprocess import Popen, PIPE 
from textwrap import dedent 
from threading import Thread 

try: 
    import Tkinter as tk 
    from Queue import Queue, Empty 
except ImportError: 
    import tkinter as tk # Python 3 
    from queue import Queue, Empty # Python 3 

def iter_except(function, exception): 
    """Works like builtin 2-argument `iter()`, but stops on `exception`.""" 
    try: 
     while True: 
      yield function() 
    except exception: 
     return 

class DisplaySubprocessOutputDemo: 
    def __init__(self, root): 
     self.root = root 

     # start dummy subprocess to generate some output 
     self.process = Popen([sys.executable, "-u", "-c", dedent(""" 
      import itertools, time 

      for i in itertools.count(): 
       print("%d.%d" % divmod(i, 10)) 
       time.sleep(0.1) 
      """)], stdout=PIPE) 

     # launch thread to read the subprocess output 
     # (put the subprocess output into the queue in a background thread, 
     # get output from the queue in the GUI thread. 
     # Output chain: process.readline -> queue -> label) 
     q = Queue(maxsize=1024) # limit output buffering (may stall subprocess) 
     t = Thread(target=self.reader_thread, args=[q]) 
     t.daemon = True # close pipe if GUI process exits 
     t.start() 

     # show subprocess' stdout in GUI 
     self.label = tk.Label(root, text=" ", font=(None, 200)) 
     self.label.pack(ipadx=4, padx=4, ipady=4, pady=4, fill='both') 
     self.update(q) # start update loop 

    def reader_thread(self, q): 
     """Read subprocess output and put it into the queue.""" 
     try: 
      with self.process.stdout as pipe: 
       for line in iter(pipe.readline, b''): 
        q.put(line) 
     finally: 
      q.put(None) 

    def update(self, q): 
     """Update GUI with items from the queue.""" 
     for line in iter_except(q.get_nowait, Empty): # display all content 
      if line is None: 
       self.quit() 
       return 
      else: 
       self.label['text'] = line # update GUI 
       break # display no more than one line per 40 milliseconds 
     self.root.after(40, self.update, q) # schedule next update 

    def quit(self): 
     self.process.kill() # exit subprocess if GUI is closed (zombie!) 
     self.root.destroy() 


root = tk.Tk() 
app = DisplaySubprocessOutputDemo(root) 
root.protocol("WM_DELETE_WINDOW", app.quit) 
# center window 
root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id())) 
root.mainloop() 

La esencia de la solución es:

  • cifran la producción de sub-proceso en la cola en un subproceso de fondo
  • obtener la salida de la cola en el hilo GUI.

es decir, llame al process.readline() en el hilo de fondo -> queue -> actualice la etiqueta de la GUI en el hilo principal.

Cuestiones relacionadas